From f08af82370125c9f4011d48aedfcaf2a645c812e Mon Sep 17 00:00:00 2001 From: Athul Mallappallil Date: Fri, 24 Apr 2026 15:03:23 +0000 Subject: [PATCH 1/7] Initial pitch commit by ETAS More details can be found in the ptich presentation #2220 Also-by: athul.mallappallil@etas.com, oliver.heilwagen@etas.com, sebastian.lobsinger@etas.com, guruprasad.bhat@etas.com, sunilkumar.prasanchand@etas.com, tibor.zavartkay@etas.com --- .bazelrc | 94 ++ .devcontainer/devcontainer.json | 31 + .github/CODEOWNERS | 15 - .github/PULL_REQUEST_TEMPLATE/bug_fix.md | 19 - .github/PULL_REQUEST_TEMPLATE/improvement.md | 19 - .github/actions/gitlint/action.yml | 44 - .github/workflows/copyright.yml | 24 - .github/workflows/docs-cleanup.yml | 29 - .github/workflows/docs.yml | 43 - .github/workflows/format.yml | 26 - .github/workflows/gitlint.yml | 31 - .github/workflows/license_check.yml | 32 - .gitignore | 8 +- BUILD | 51 +- CONTRIBUTION.md | 35 - LICENSE | 177 --- MODULE.bazel | 247 +++- NOTICE | 32 - README.md | 70 +- THIRD_PARTY_NOTICES | 20 + docs/conf.py | 6 +- docs/crypto/architecture/api_architecture.rst | 60 + .../api_certificate_contexts.puml | 127 +++ docs/crypto/architecture/api_contexts.puml | 145 +++ docs/crypto/architecture/api_description.rst | 632 ++++++++++ docs/crypto/architecture/api_overview.puml | 86 ++ .../architecture/api_resource_management.puml | 47 + .../architecture/api_typed_objects.puml | 104 ++ .../architecture/component_overview.png | Bin 0 -> 165939 bytes .../architecture/component_overview.puml | 179 +++ .../architecture/configuration_objects.puml | 128 +++ docs/crypto/architecture/design_decisions.rst | 1014 +++++++++++++++++ .../architecture/dynamic_architecture.rst | 298 +++++ docs/crypto/architecture/index.rst | 194 ++++ docs/crypto/architecture/interfaces.rst | 418 +++++++ .../key_configuration_params.puml | 107 ++ .../key_management_class_diagram.puml | 561 +++++++++ .../architecture/key_management_details.rst | 686 +++++++++++ .../key_management_sequence_diagrams.puml | 331 ++++++ .../architecture/provider_architecture.puml | 199 ++++ .../architecture/provider_architecture.rst | 107 ++ .../architecture/typical_usage_sequence.puml | 94 ++ docs/index.rst | 109 +- examples/BUILD | 48 +- examples/README.md | 5 + examples/hashing_example.cpp | 178 +++ examples/memory_allocator_example.cpp | 144 +++ platforms/BUILD | 49 + project_config.bzl | 2 +- score/crypto/api/BUILD | 27 + score/crypto/api/control_plane/BUILD | 47 + .../api/control_plane/connection_factory.hpp | 47 + .../crypto/api/control_plane/i_connection.hpp | 53 + .../control_plane/src/connection_factory.cpp | 52 + .../api/control_plane/src/connection_impl.cpp | 84 ++ .../api/control_plane/src/connection_impl.hpp | 55 + score/crypto/common/BUILD | 31 + score/crypto/common/types.hpp | 164 +++ score/crypto/daemon/BUILD | 58 + score/crypto/daemon/common/BUILD | 49 + score/crypto/daemon/common/actors.hpp | 35 + score/crypto/daemon/common/algorithm_info.hpp | 126 ++ score/crypto/daemon/common/daemon_error.hpp | 284 +++++ .../crypto/daemon/common/operation_names.hpp | 217 ++++ score/crypto/daemon/common/secure_memory.hpp | 75 ++ score/crypto/daemon/common/types.hpp | 175 +++ score/crypto/daemon/config/BUILD | 76 ++ score/crypto/daemon/config/crypto_config.fbs | 66 ++ score/crypto/daemon/config/inc/config.hpp | 471 ++++++++ score/crypto/daemon/config/src/config.cpp | 133 +++ .../config/src/flatbuffer_config_parser.cpp | 287 +++++ .../config/src/flatbuffer_config_parser.hpp | 143 +++ score/crypto/daemon/control_plane/BUILD | 62 + .../basic_handler_chain_factory.hpp | 76 ++ .../daemon/control_plane/control_operations.h | 65 ++ .../daemon/control_plane/control_protocol.h | 865 ++++++++++++++ .../daemon/control_plane/i_control_server.h | 40 + .../control_plane/i_handler_chain_factory.hpp | 64 ++ .../control_plane/i_request_handler.hpp | 67 ++ .../src/basic_handler_chain_factory.cpp | 52 + .../control_plane/src/connection_handler.cpp | 162 +++ .../control_plane/src/connection_handler.hpp | 66 ++ score/crypto/daemon/data_manager/BUILD | 35 + .../daemon/data_manager/data_manager.hpp | 203 ++++ .../crypto/daemon/data_manager/data_node.hpp | 226 ++++ .../data_manager/data_node_accessor.hpp | 216 ++++ .../crypto/daemon/data_manager/docs/index.rst | 124 ++ .../daemon/data_manager/i_data_manager.hpp | 189 +++ .../daemon/data_manager/src/data_manager.cpp | 529 +++++++++ .../daemon/data_manager/src/data_node.cpp | 116 ++ .../src/data_node_manager_token.hpp | 41 + score/crypto/daemon/key_management/BUILD | 117 ++ .../daemon/key_management/core/key_entry.hpp | 127 +++ .../core/key_management_service.cpp | 594 ++++++++++ .../core/key_management_service.hpp | 262 +++++ .../key_management/core/key_registry.cpp | 186 +++ .../key_management/core/key_registry.hpp | 140 +++ .../detail/slot_info_builder.hpp | 31 + .../interfaces/i_key_factory.cpp | 134 +++ .../interfaces/i_key_factory.hpp | 156 +++ .../interfaces/i_key_handler.hpp | 83 ++ .../interfaces/i_key_slot_catalog.hpp | 54 + .../interfaces/i_key_slot_handler.cpp | 33 + .../interfaces/i_key_slot_handler.hpp | 91 ++ .../interfaces/key_management_operations.hpp | 82 ++ .../interfaces/key_slot_config.hpp | 219 ++++ .../key_management/interfaces/key_types.hpp | 183 +++ .../key_management/key_management_module.cpp | 78 ++ .../key_management/key_management_module.hpp | 88 ++ .../key_management/nodes/key_data_node.hpp | 110 ++ .../nodes/key_slot_data_node.hpp | 100 ++ .../slot/access_policy_enforcer.cpp | 108 ++ .../slot/access_policy_enforcer.hpp | 91 ++ .../slot/config_driven_slot_catalog.cpp | 132 +++ .../slot/config_driven_slot_catalog.hpp | 63 + .../key_management/slot/deployment/BUILD | 48 + .../slot/deployment/deployment_path_utils.hpp | 51 + .../slot/deployment/i_deployment_loader.hpp | 53 + .../slot/deployment/i_deployment_writer.hpp | 54 + .../deployment/kv/kv_deployment_loader.cpp | 118 ++ .../deployment/kv/kv_deployment_loader.hpp | 54 + .../deployment/kv/kv_deployment_writer.cpp | 54 + .../deployment/kv/kv_deployment_writer.hpp | 44 + .../key_management/slot/deployment_loader.cpp | 45 + .../key_management/slot/deployment_loader.hpp | 69 ++ .../key_management/slot/deployment_writer.cpp | 44 + .../key_management/slot/deployment_writer.hpp | 60 + .../slot/file_backed_slot_handler.cpp | 132 +++ .../slot/file_backed_slot_handler.hpp | 87 ++ .../key_management/slot/slot_registry.cpp | 176 +++ .../key_management/slot/slot_registry.hpp | 242 ++++ score/crypto/daemon/mediator/BUILD | 47 + score/crypto/daemon/mediator/i_mediator.hpp | 84 ++ .../daemon/mediator/mediator_operations.hpp | 84 ++ .../daemon/mediator/src/mediator_impl.cpp | 582 ++++++++++ .../daemon/mediator/src/mediator_impl.hpp | 116 ++ score/crypto/daemon/provider/BUILD | 38 + score/crypto/daemon/provider/executors/BUILD | 32 + .../provider/executors/key_mgmt_context.hpp | 37 + .../provider/executors/key_mgmt_executor.hpp | 82 ++ .../executors/key_mgmt_request_parser.hpp | 119 ++ .../executors/src/key_mgmt_executor.cpp | 251 ++++ score/crypto/daemon/provider/handler/BUILD | 100 ++ .../provider/handler/context_data_node.hpp | 57 + .../provider/handler/handler_init_params.hpp | 55 + .../handler/i_crypto_handler_factory.hpp | 57 + .../daemon/provider/handler/i_handler.hpp | 44 + .../operations/hash_handler_operations.hpp | 101 ++ .../operations/mac_handler_operations.hpp | 103 ++ .../handler/src/context_data_node.cpp | 41 + .../provider/handler/src/handler_utils.cpp | 138 +++ .../provider/handler/src/handler_utils.hpp | 119 ++ score/crypto/daemon/provider/i_provider.hpp | 131 +++ .../daemon/provider/i_provider_factory.hpp | 64 ++ score/crypto/daemon/provider/pkcs11/BUILD | 96 ++ .../pkcs11/detail/pkcs11_algorithm_info.hpp | 169 +++ .../key_management/pkcs11_key_factory.cpp | 233 ++++ .../key_management/pkcs11_key_factory.hpp | 77 ++ .../key_management/pkcs11_key_handler.cpp | 85 ++ .../key_management/pkcs11_key_handler.hpp | 91 ++ .../pkcs11_key_slot_handler.cpp | 281 +++++ .../pkcs11_key_slot_handler.hpp | 77 ++ .../key_management/pkcs11_key_store.cpp | 247 ++++ .../key_management/pkcs11_key_store.hpp | 197 ++++ .../factory/pkcs11_handler_factory.cpp | 139 +++ .../factory/pkcs11_handler_factory.hpp | 75 ++ .../operations/hash/pkcs11_hash_context.hpp | 39 + .../operations/hash/pkcs11_hash_executor.cpp | 288 +++++ .../operations/hash/pkcs11_hash_executor.hpp | 114 ++ .../operations/hash/pkcs11_hash_handler.cpp | 221 ++++ .../operations/hash/pkcs11_hash_handler.hpp | 105 ++ .../pkcs11_key_management_handler.cpp | 72 ++ .../pkcs11_key_management_handler.hpp | 99 ++ .../operations/mac/pkcs11_mac_context.hpp | 43 + .../operations/mac/pkcs11_mac_executor.cpp | 635 +++++++++++ .../operations/mac/pkcs11_mac_executor.hpp | 213 ++++ .../operations/mac/pkcs11_mac_handler.cpp | 256 +++++ .../operations/mac/pkcs11_mac_handler.hpp | 129 +++ .../daemon/provider/pkcs11/pkcs11_module.cpp | 465 ++++++++ .../daemon/provider/pkcs11/pkcs11_module.hpp | 321 ++++++ .../provider/pkcs11/pkcs11_provider.cpp | 423 +++++++ .../provider/pkcs11/pkcs11_provider.hpp | 197 ++++ .../pkcs11/pkcs11_provider_factory.cpp | 65 ++ .../pkcs11/pkcs11_provider_factory.hpp | 84 ++ .../provider/pkcs11/pkcs11_session_guard.hpp | 138 +++ .../provider/pkcs11/pkcs11_token_config.cpp | 55 + .../provider/pkcs11/pkcs11_token_config.hpp | 99 ++ .../daemon/provider/provider_manager.hpp | 284 +++++ .../daemon/provider/score_provider/BUILD | 49 + .../provider/score_provider/openssl/BUILD | 100 ++ .../openssl/detail/openssl_algorithm_info.hpp | 87 ++ .../key_management/openssl_key_factory.cpp | 88 ++ .../key_management/openssl_key_factory.hpp | 74 ++ .../key_management/openssl_key_handler.cpp | 84 ++ .../key_management/openssl_key_handler.hpp | 66 ++ .../openssl/openssl_provider_factory.cpp | 31 + .../openssl/openssl_provider_factory.hpp | 47 + .../factory/openssl_handler_factory.cpp | 71 ++ .../factory/openssl_handler_factory.hpp | 43 + .../operations/hash/openssl_hash_handler.cpp | 375 ++++++ .../operations/hash/openssl_hash_handler.hpp | 76 ++ .../openssl_key_management_handler.cpp | 24 + .../openssl_key_management_handler.hpp | 41 + .../operations/mac/openssl_hmac_handler.cpp | 418 +++++++ .../operations/mac/openssl_hmac_handler.hpp | 110 ++ .../openssl/provider_openssl.cpp | 95 ++ .../openssl/provider_openssl.hpp | 62 + .../score_provider/operations/factory/BUILD | 27 + .../factory/score_handler_factory.hpp | 74 ++ .../factory/src/score_handler_factory.cpp | 83 ++ .../score_provider/operations/hash/BUILD | 34 + .../operations/hash/hash_executor.hpp | 73 ++ .../operations/hash/score_hash_handler.hpp | 126 ++ .../operations/hash/src/hash_executor.cpp | 227 ++++ .../hash/src/score_hash_handler.cpp | 85 ++ .../operations/key_management/BUILD | 25 + .../score_key_management_handler.hpp | 62 + .../src/score_key_management_handler.cpp | 60 + .../score_provider/operations/mac/BUILD | 33 + .../operations/mac/mac_executor.hpp | 75 ++ .../operations/mac/score_mac_handler.hpp | 122 ++ .../operations/mac/src/mac_executor.cpp | 261 +++++ .../operations/mac/src/score_mac_handler.cpp | 79 ++ .../score_provider/score_provider.hpp | 66 ++ .../score_provider/score_provider_config.hpp | 91 ++ .../score_provider/score_provider_factory.hpp | 68 ++ .../score_provider/src/score_provider.cpp | 64 ++ .../src/score_provider_config.cpp | 37 + .../src/score_provider_factory.cpp | 52 + .../daemon/provider/src/provider_manager.cpp | 279 +++++ score/crypto/daemon/src/daemon.cpp | 130 +++ score/crypto/ipc/BUILD | 20 + score/crypto/ipc/grpc_adapter/BUILD | 75 ++ score/crypto/ipc/grpc_adapter/control.fbs | 138 +++ .../ipc/grpc_adapter/grpc_control_client.h | 58 + .../ipc/grpc_adapter/grpc_control_handler.h | 64 ++ .../ipc/grpc_adapter/grpc_control_server.h | 51 + .../grpc_adapter/src/grpc_control_client.cpp | 413 +++++++ .../grpc_adapter/src/grpc_control_handler.cpp | 391 +++++++ .../grpc_adapter/src/grpc_control_server.cpp | 106 ++ .../ipc/grpc_adapter/src/grpc_id_helpers.cpp | 48 + .../ipc/grpc_adapter/src/grpc_id_helpers.h | 47 + score/crypto/ipc/ipc_config.h | 26 + score/mw/crypto/api/BUILD | 44 + score/mw/crypto/api/common/BUILD | 52 + .../api/common/crypto_resource_guard.hpp | 144 +++ score/mw/crypto/api/common/error_domain.hpp | 150 +++ .../api/common/fixed_capacity_string.hpp | 327 ++++++ .../crypto/api/common/i_memory_allocator.hpp | 85 ++ .../mw/crypto/api/common/i_memory_region.hpp | 103 ++ .../api/common/src/crypto_resource_guard.cpp | 103 ++ .../src/crypto_resource_guard_factory.hpp | 107 ++ .../mw/crypto/api/common/src/error_domain.cpp | 156 +++ .../api/common/src/i_release_callback.hpp | 66 ++ score/mw/crypto/api/common/types.hpp | 442 +++++++ score/mw/crypto/api/config/BUILD | 30 + .../crypto/api/config/base_context_config.hpp | 192 ++++ .../crypto/api/config/hash_context_config.hpp | 68 ++ .../config/key_management_context_config.hpp | 65 ++ .../api/config/key_operation_params.hpp | 585 ++++++++++ .../crypto/api/config/mac_context_config.hpp | 85 ++ .../crypto/api/config/permission_builder.hpp | 153 +++ score/mw/crypto/api/contexts/BUILD | 71 ++ score/mw/crypto/api/contexts/i_context.hpp | 53 + .../mw/crypto/api/contexts/i_hash_context.hpp | 78 ++ .../api/contexts/i_key_management_context.hpp | 299 +++++ .../mw/crypto/api/contexts/i_mac_context.hpp | 75 ++ .../api/contexts/i_streaming_context.hpp | 106 ++ .../contexts/i_streaming_output_context.hpp | 70 ++ .../api/contexts/src/hash_context_impl.cpp | 350 ++++++ .../api/contexts/src/hash_context_impl.hpp | 75 ++ .../src/key_management_context_impl.cpp | 419 +++++++ .../src/key_management_context_impl.hpp | 141 +++ .../api/contexts/src/mac_context_impl.cpp | 309 +++++ .../api/contexts/src/mac_context_impl.hpp | 74 ++ score/mw/crypto/api/crypto_stack_factory.hpp | 88 ++ score/mw/crypto/api/future/certificate/BUILD | 31 + .../api/future/certificate/cert_types.hpp | 55 + .../api/future/certificate/i_csr_export.hpp | 64 ++ .../certificate/i_ocsp_request_export.hpp | 64 ++ score/mw/crypto/api/future/common/BUILD | 26 + .../common/i_secure_storage_manager.hpp | 93 ++ score/mw/crypto/api/future/config/BUILD | 35 + .../api/future/config/aead_context_config.hpp | 91 ++ .../config/certificate_context_config.hpp | 70 ++ ...ertificate_verification_context_config.hpp | 89 ++ .../future/config/cipher_context_config.hpp | 103 ++ .../config/csr_generation_context_config.hpp | 73 ++ .../future/config/random_context_config.hpp | 70 ++ .../api/future/config/sign_context_config.hpp | 79 ++ .../verify_signature_context_config.hpp | 79 ++ score/mw/crypto/api/future/contexts/BUILD | 78 ++ .../api/future/contexts/i_aead_context.hpp | 104 ++ .../i_certificate_management_context.hpp | 246 ++++ .../i_certificate_verification_context.hpp | 155 +++ .../api/future/contexts/i_cipher_context.hpp | 118 ++ .../contexts/i_csr_generation_context.hpp | 104 ++ .../api/future/contexts/i_random_context.hpp | 71 ++ .../api/future/contexts/i_sign_context.hpp | 84 ++ .../contexts/i_verify_signature_context.hpp | 77 ++ .../src/i_certificate_context_impl_guide.hpp | 73 ++ score/mw/crypto/api/future/objects/BUILD | 33 + .../api/future/objects/i_cert_slot_object.hpp | 54 + .../future/objects/i_certificate_object.hpp | 108 ++ .../api/future/objects/i_data_object.hpp | 63 + .../api/future/objects/i_provider_object.hpp | 63 + .../api/future/objects/i_secure_object.hpp | 63 + score/mw/crypto/api/i_crypto_context.hpp | 241 ++++ score/mw/crypto/api/i_crypto_stack.hpp | 76 ++ score/mw/crypto/api/objects/BUILD | 32 + .../mw/crypto/api/objects/i_crypto_object.hpp | 61 + score/mw/crypto/api/objects/i_key_object.hpp | 78 ++ .../crypto/api/objects/i_key_slot_object.hpp | 81 ++ .../api/objects/i_private_key_object.hpp | 71 ++ .../api/objects/i_public_key_object.hpp | 60 + .../api/objects/i_symmetric_key_object.hpp | 57 + .../mw/crypto/api/src/crypto_context_impl.cpp | 403 +++++++ .../mw/crypto/api/src/crypto_context_impl.hpp | 81 ++ .../crypto/api/src/crypto_stack_factory.cpp | 106 ++ score/mw/crypto/api/src/crypto_stack_impl.cpp | 76 ++ score/mw/crypto/api/src/crypto_stack_impl.hpp | 64 ++ .../api/src/provider_type_converter.hpp | 56 + src/BUILD | 0 tests/README.md | 57 + tests/cpp/BUILD | 19 - tests/cpp/test_main.cpp | 41 - tests/demo/BUILD | 29 + tests/demo/mac_multi_provider_demo.cpp | 337 ++++++ tests/grpc_control_plane/BUILD | 29 + .../grpc_control_plane/test_control_plane.cpp | 226 ++++ tests/integration_tests/BUILD | 102 ++ tests/integration_tests/DEMO.md | 37 + .../integration_tests/control_client_app.cpp | 587 ++++++++++ .../integration_tests/init_softhsm_token.cpp | 396 +++++++ tests/integration_tests/integration_test.py | 356 ++++++ .../score_api_hash_example.cpp | 227 ++++ .../score_api_mac_example.cpp | 630 ++++++++++ tests/integration_tests/score_demo.cpp | 266 +++++ tests/key_management/BUILD | 83 ++ tests/key_management/config/BUILD | 29 + .../config/key_management_test_config.json | 69 ++ .../test_access_policy_enforcer.cpp | 124 ++ .../test_key_config_manager.cpp | 131 +++ .../test_key_management_context.cpp | 344 ++++++ .../test_openssl_key_handler.cpp | 235 ++++ .../test_pkcs11_key_handler.cpp | 318 ++++++ tests/openssl/block_cipher/ECB-AES128/BUILD | 32 + .../ECB-AES128/test_ecb_aes128.cpp | 70 ++ tests/provider_test/BUILD | 69 ++ .../provider_test/test_pkcs11_multi_token.cpp | 236 ++++ tests/provider_test/test_pkcs11_provider.cpp | 432 +++++++ tests/provider_test/test_provider.cpp | 289 +++++ tests/provider_test/test_provider_manager.cpp | 151 +++ tests/rust/BUILD | 18 - tests/rust/test_main.rs | 4 - tests/softhsm/BUILD | 25 + tests/softhsm/block_cipher/ECB-AES128/BUILD | 28 + .../ECB-AES128/test_softhsm_ecb_aes128.cpp | 225 ++++ tests/softhsm/softhsm_test_fixture.hpp | 265 +++++ .../block_cipher/ECB-AES128/BUILD | 22 + .../block_cipher/ECB-AES128/block1.bin | 1 + .../block_cipher/ECB-AES128/block1.enc | 1 + .../block_cipher/ECB-AES128/block2.bin | 1 + .../block_cipher/ECB-AES128/block2.enc | 1 + .../block_cipher/ECB-AES128/block3.bin | 2 + .../block_cipher/ECB-AES128/block3.enc | Bin 0 -> 16 bytes .../block_cipher/ECB-AES128/block4.bin | 1 + .../block_cipher/ECB-AES128/block4.enc | 1 + .../block_cipher/ECB-AES128/key.bin | 1 + .../block_cipher/ECB-AES128/reference.md | 24 + tests/test_vectors/config/BUILD | 33 + .../config/integration_openssl_hmac.kv | 5 + .../config/integration_softhsm_hmac.kv | 4 + .../config/integration_test_config.json | 109 ++ tests/test_vectors/hash/BUILD | 19 + .../test_vectors/hash/input_complete_data.bin | 1 + tests/test_vectors/hash/input_hello_world.bin | 1 + tests/test_vectors/hash/reference.md | 22 + .../hash/sha256_complete_data.bin | 1 + .../test_vectors/hash/sha256_hello_world.bin | 2 + .../test_vectors/hash/sha384_hello_world.bin | 1 + tests/test_vectors/key_management/BUILD | 20 + .../key_management/aes256_cmac.key | 1 + .../key_management/hmac_sha256.key | 1 + tests/test_vectors/mac/BUILD | 22 + .../mac/hmac_sha256_complete_data.bin | 1 + .../mac/hmac_sha256_hello_world.bin | 1 + .../test_vectors/mac/hmac_sha256_hi_there.bin | Bin 0 -> 32 bytes .../test_vectors/mac/input_complete_data.bin | 1 + tests/test_vectors/mac/input_hello_world.bin | 1 + tests/test_vectors/mac/input_hi_there.bin | 1 + tests/test_vectors/mac/key_aes_256.key | 1 + tests/test_vectors/mac/reference.md | 14 + tests/utility/BUILD | 23 + tests/utility/test_utility.cpp | 75 ++ tests/utility/test_utility.hpp | 159 +++ third_party/grpc/BUILD | 38 + third_party/grpc/abseil_qnx_pthread.patch | 52 + third_party/grpc/grpc_qnx_pthread.patch | 18 + third_party/grpc/grpc_symbol_reference.cpp | 65 ++ third_party/grpc/protobuf_qnx_pthread.patch | 10 + third_party/openssl/BUILD | 137 +++ third_party/soft_hsm/BUILD | 101 ++ third_party/soft_hsm/softhsm_qnx_dl.patch | 19 + 404 files changed, 47845 insertions(+), 765 deletions(-) create mode 100644 .devcontainer/devcontainer.json delete mode 100644 .github/CODEOWNERS delete mode 100644 .github/PULL_REQUEST_TEMPLATE/bug_fix.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE/improvement.md delete mode 100644 .github/actions/gitlint/action.yml delete mode 100644 .github/workflows/copyright.yml delete mode 100644 .github/workflows/docs-cleanup.yml delete mode 100644 .github/workflows/docs.yml delete mode 100644 .github/workflows/format.yml delete mode 100644 .github/workflows/gitlint.yml delete mode 100644 .github/workflows/license_check.yml delete mode 100644 CONTRIBUTION.md delete mode 100644 LICENSE delete mode 100644 NOTICE create mode 100644 THIRD_PARTY_NOTICES create mode 100644 docs/crypto/architecture/api_architecture.rst create mode 100644 docs/crypto/architecture/api_certificate_contexts.puml create mode 100644 docs/crypto/architecture/api_contexts.puml create mode 100644 docs/crypto/architecture/api_description.rst create mode 100644 docs/crypto/architecture/api_overview.puml create mode 100644 docs/crypto/architecture/api_resource_management.puml create mode 100644 docs/crypto/architecture/api_typed_objects.puml create mode 100644 docs/crypto/architecture/component_overview.png create mode 100644 docs/crypto/architecture/component_overview.puml create mode 100644 docs/crypto/architecture/configuration_objects.puml create mode 100644 docs/crypto/architecture/design_decisions.rst create mode 100644 docs/crypto/architecture/dynamic_architecture.rst create mode 100644 docs/crypto/architecture/index.rst create mode 100644 docs/crypto/architecture/interfaces.rst create mode 100644 docs/crypto/architecture/key_configuration_params.puml create mode 100644 docs/crypto/architecture/key_management_class_diagram.puml create mode 100644 docs/crypto/architecture/key_management_details.rst create mode 100644 docs/crypto/architecture/key_management_sequence_diagrams.puml create mode 100644 docs/crypto/architecture/provider_architecture.puml create mode 100644 docs/crypto/architecture/provider_architecture.rst create mode 100644 docs/crypto/architecture/typical_usage_sequence.puml create mode 100644 examples/README.md create mode 100644 examples/hashing_example.cpp create mode 100644 examples/memory_allocator_example.cpp create mode 100644 platforms/BUILD create mode 100644 score/crypto/api/BUILD create mode 100644 score/crypto/api/control_plane/BUILD create mode 100644 score/crypto/api/control_plane/connection_factory.hpp create mode 100644 score/crypto/api/control_plane/i_connection.hpp create mode 100644 score/crypto/api/control_plane/src/connection_factory.cpp create mode 100644 score/crypto/api/control_plane/src/connection_impl.cpp create mode 100644 score/crypto/api/control_plane/src/connection_impl.hpp create mode 100644 score/crypto/common/BUILD create mode 100644 score/crypto/common/types.hpp create mode 100644 score/crypto/daemon/BUILD create mode 100644 score/crypto/daemon/common/BUILD create mode 100644 score/crypto/daemon/common/actors.hpp create mode 100644 score/crypto/daemon/common/algorithm_info.hpp create mode 100644 score/crypto/daemon/common/daemon_error.hpp create mode 100644 score/crypto/daemon/common/operation_names.hpp create mode 100644 score/crypto/daemon/common/secure_memory.hpp create mode 100644 score/crypto/daemon/common/types.hpp create mode 100644 score/crypto/daemon/config/BUILD create mode 100644 score/crypto/daemon/config/crypto_config.fbs create mode 100644 score/crypto/daemon/config/inc/config.hpp create mode 100644 score/crypto/daemon/config/src/config.cpp create mode 100644 score/crypto/daemon/config/src/flatbuffer_config_parser.cpp create mode 100644 score/crypto/daemon/config/src/flatbuffer_config_parser.hpp create mode 100644 score/crypto/daemon/control_plane/BUILD create mode 100644 score/crypto/daemon/control_plane/basic_handler_chain_factory.hpp create mode 100644 score/crypto/daemon/control_plane/control_operations.h create mode 100644 score/crypto/daemon/control_plane/control_protocol.h create mode 100644 score/crypto/daemon/control_plane/i_control_server.h create mode 100644 score/crypto/daemon/control_plane/i_handler_chain_factory.hpp create mode 100644 score/crypto/daemon/control_plane/i_request_handler.hpp create mode 100644 score/crypto/daemon/control_plane/src/basic_handler_chain_factory.cpp create mode 100644 score/crypto/daemon/control_plane/src/connection_handler.cpp create mode 100644 score/crypto/daemon/control_plane/src/connection_handler.hpp create mode 100644 score/crypto/daemon/data_manager/BUILD create mode 100644 score/crypto/daemon/data_manager/data_manager.hpp create mode 100644 score/crypto/daemon/data_manager/data_node.hpp create mode 100644 score/crypto/daemon/data_manager/data_node_accessor.hpp create mode 100644 score/crypto/daemon/data_manager/docs/index.rst create mode 100644 score/crypto/daemon/data_manager/i_data_manager.hpp create mode 100644 score/crypto/daemon/data_manager/src/data_manager.cpp create mode 100644 score/crypto/daemon/data_manager/src/data_node.cpp create mode 100644 score/crypto/daemon/data_manager/src/data_node_manager_token.hpp create mode 100644 score/crypto/daemon/key_management/BUILD create mode 100644 score/crypto/daemon/key_management/core/key_entry.hpp create mode 100644 score/crypto/daemon/key_management/core/key_management_service.cpp create mode 100644 score/crypto/daemon/key_management/core/key_management_service.hpp create mode 100644 score/crypto/daemon/key_management/core/key_registry.cpp create mode 100644 score/crypto/daemon/key_management/core/key_registry.hpp create mode 100644 score/crypto/daemon/key_management/detail/slot_info_builder.hpp create mode 100644 score/crypto/daemon/key_management/interfaces/i_key_factory.cpp create mode 100644 score/crypto/daemon/key_management/interfaces/i_key_factory.hpp create mode 100644 score/crypto/daemon/key_management/interfaces/i_key_handler.hpp create mode 100644 score/crypto/daemon/key_management/interfaces/i_key_slot_catalog.hpp create mode 100644 score/crypto/daemon/key_management/interfaces/i_key_slot_handler.cpp create mode 100644 score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp create mode 100644 score/crypto/daemon/key_management/interfaces/key_management_operations.hpp create mode 100644 score/crypto/daemon/key_management/interfaces/key_slot_config.hpp create mode 100644 score/crypto/daemon/key_management/interfaces/key_types.hpp create mode 100644 score/crypto/daemon/key_management/key_management_module.cpp create mode 100644 score/crypto/daemon/key_management/key_management_module.hpp create mode 100644 score/crypto/daemon/key_management/nodes/key_data_node.hpp create mode 100644 score/crypto/daemon/key_management/nodes/key_slot_data_node.hpp create mode 100644 score/crypto/daemon/key_management/slot/access_policy_enforcer.cpp create mode 100644 score/crypto/daemon/key_management/slot/access_policy_enforcer.hpp create mode 100644 score/crypto/daemon/key_management/slot/config_driven_slot_catalog.cpp create mode 100644 score/crypto/daemon/key_management/slot/config_driven_slot_catalog.hpp create mode 100644 score/crypto/daemon/key_management/slot/deployment/BUILD create mode 100644 score/crypto/daemon/key_management/slot/deployment/deployment_path_utils.hpp create mode 100644 score/crypto/daemon/key_management/slot/deployment/i_deployment_loader.hpp create mode 100644 score/crypto/daemon/key_management/slot/deployment/i_deployment_writer.hpp create mode 100644 score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.cpp create mode 100644 score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.hpp create mode 100644 score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.cpp create mode 100644 score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.hpp create mode 100644 score/crypto/daemon/key_management/slot/deployment_loader.cpp create mode 100644 score/crypto/daemon/key_management/slot/deployment_loader.hpp create mode 100644 score/crypto/daemon/key_management/slot/deployment_writer.cpp create mode 100644 score/crypto/daemon/key_management/slot/deployment_writer.hpp create mode 100644 score/crypto/daemon/key_management/slot/file_backed_slot_handler.cpp create mode 100644 score/crypto/daemon/key_management/slot/file_backed_slot_handler.hpp create mode 100644 score/crypto/daemon/key_management/slot/slot_registry.cpp create mode 100644 score/crypto/daemon/key_management/slot/slot_registry.hpp create mode 100644 score/crypto/daemon/mediator/BUILD create mode 100644 score/crypto/daemon/mediator/i_mediator.hpp create mode 100644 score/crypto/daemon/mediator/mediator_operations.hpp create mode 100644 score/crypto/daemon/mediator/src/mediator_impl.cpp create mode 100644 score/crypto/daemon/mediator/src/mediator_impl.hpp create mode 100644 score/crypto/daemon/provider/BUILD create mode 100644 score/crypto/daemon/provider/executors/BUILD create mode 100644 score/crypto/daemon/provider/executors/key_mgmt_context.hpp create mode 100644 score/crypto/daemon/provider/executors/key_mgmt_executor.hpp create mode 100644 score/crypto/daemon/provider/executors/key_mgmt_request_parser.hpp create mode 100644 score/crypto/daemon/provider/executors/src/key_mgmt_executor.cpp create mode 100644 score/crypto/daemon/provider/handler/BUILD create mode 100644 score/crypto/daemon/provider/handler/context_data_node.hpp create mode 100644 score/crypto/daemon/provider/handler/handler_init_params.hpp create mode 100644 score/crypto/daemon/provider/handler/i_crypto_handler_factory.hpp create mode 100644 score/crypto/daemon/provider/handler/i_handler.hpp create mode 100644 score/crypto/daemon/provider/handler/operations/hash_handler_operations.hpp create mode 100644 score/crypto/daemon/provider/handler/operations/mac_handler_operations.hpp create mode 100644 score/crypto/daemon/provider/handler/src/context_data_node.cpp create mode 100644 score/crypto/daemon/provider/handler/src/handler_utils.cpp create mode 100644 score/crypto/daemon/provider/handler/src/handler_utils.hpp create mode 100644 score/crypto/daemon/provider/i_provider.hpp create mode 100644 score/crypto/daemon/provider/i_provider_factory.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/BUILD create mode 100644 score/crypto/daemon/provider/pkcs11/detail/pkcs11_algorithm_info.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_factory.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_factory.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_slot_handler.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_slot_handler.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/factory/pkcs11_handler_factory.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/factory/pkcs11_handler_factory.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_context.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_handler.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_handler.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_context.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_handler.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_handler.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/pkcs11_module.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/pkcs11_provider.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/pkcs11_session_guard.hpp create mode 100644 score/crypto/daemon/provider/pkcs11/pkcs11_token_config.cpp create mode 100644 score/crypto/daemon/provider/pkcs11/pkcs11_token_config.hpp create mode 100644 score/crypto/daemon/provider/provider_manager.hpp create mode 100644 score/crypto/daemon/provider/score_provider/BUILD create mode 100644 score/crypto/daemon/provider/score_provider/openssl/BUILD create mode 100644 score/crypto/daemon/provider/score_provider/openssl/detail/openssl_algorithm_info.hpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_factory.cpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_factory.hpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.cpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.hpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/openssl_provider_factory.cpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/openssl_provider_factory.hpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/operations/factory/openssl_handler_factory.cpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/operations/factory/openssl_handler_factory.hpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/operations/hash/openssl_hash_handler.cpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/operations/hash/openssl_hash_handler.hpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/operations/key_management/openssl_key_management_handler.cpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/operations/key_management/openssl_key_management_handler.hpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.cpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.hpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/provider_openssl.cpp create mode 100644 score/crypto/daemon/provider/score_provider/openssl/provider_openssl.hpp create mode 100644 score/crypto/daemon/provider/score_provider/operations/factory/BUILD create mode 100644 score/crypto/daemon/provider/score_provider/operations/factory/score_handler_factory.hpp create mode 100644 score/crypto/daemon/provider/score_provider/operations/factory/src/score_handler_factory.cpp create mode 100644 score/crypto/daemon/provider/score_provider/operations/hash/BUILD create mode 100644 score/crypto/daemon/provider/score_provider/operations/hash/hash_executor.hpp create mode 100644 score/crypto/daemon/provider/score_provider/operations/hash/score_hash_handler.hpp create mode 100644 score/crypto/daemon/provider/score_provider/operations/hash/src/hash_executor.cpp create mode 100644 score/crypto/daemon/provider/score_provider/operations/hash/src/score_hash_handler.cpp create mode 100644 score/crypto/daemon/provider/score_provider/operations/key_management/BUILD create mode 100644 score/crypto/daemon/provider/score_provider/operations/key_management/score_key_management_handler.hpp create mode 100644 score/crypto/daemon/provider/score_provider/operations/key_management/src/score_key_management_handler.cpp create mode 100644 score/crypto/daemon/provider/score_provider/operations/mac/BUILD create mode 100644 score/crypto/daemon/provider/score_provider/operations/mac/mac_executor.hpp create mode 100644 score/crypto/daemon/provider/score_provider/operations/mac/score_mac_handler.hpp create mode 100644 score/crypto/daemon/provider/score_provider/operations/mac/src/mac_executor.cpp create mode 100644 score/crypto/daemon/provider/score_provider/operations/mac/src/score_mac_handler.cpp create mode 100644 score/crypto/daemon/provider/score_provider/score_provider.hpp create mode 100644 score/crypto/daemon/provider/score_provider/score_provider_config.hpp create mode 100644 score/crypto/daemon/provider/score_provider/score_provider_factory.hpp create mode 100644 score/crypto/daemon/provider/score_provider/src/score_provider.cpp create mode 100644 score/crypto/daemon/provider/score_provider/src/score_provider_config.cpp create mode 100644 score/crypto/daemon/provider/score_provider/src/score_provider_factory.cpp create mode 100644 score/crypto/daemon/provider/src/provider_manager.cpp create mode 100644 score/crypto/daemon/src/daemon.cpp create mode 100644 score/crypto/ipc/BUILD create mode 100644 score/crypto/ipc/grpc_adapter/BUILD create mode 100644 score/crypto/ipc/grpc_adapter/control.fbs create mode 100644 score/crypto/ipc/grpc_adapter/grpc_control_client.h create mode 100644 score/crypto/ipc/grpc_adapter/grpc_control_handler.h create mode 100644 score/crypto/ipc/grpc_adapter/grpc_control_server.h create mode 100644 score/crypto/ipc/grpc_adapter/src/grpc_control_client.cpp create mode 100644 score/crypto/ipc/grpc_adapter/src/grpc_control_handler.cpp create mode 100644 score/crypto/ipc/grpc_adapter/src/grpc_control_server.cpp create mode 100644 score/crypto/ipc/grpc_adapter/src/grpc_id_helpers.cpp create mode 100644 score/crypto/ipc/grpc_adapter/src/grpc_id_helpers.h create mode 100644 score/crypto/ipc/ipc_config.h create mode 100644 score/mw/crypto/api/BUILD create mode 100644 score/mw/crypto/api/common/BUILD create mode 100644 score/mw/crypto/api/common/crypto_resource_guard.hpp create mode 100644 score/mw/crypto/api/common/error_domain.hpp create mode 100644 score/mw/crypto/api/common/fixed_capacity_string.hpp create mode 100644 score/mw/crypto/api/common/i_memory_allocator.hpp create mode 100644 score/mw/crypto/api/common/i_memory_region.hpp create mode 100644 score/mw/crypto/api/common/src/crypto_resource_guard.cpp create mode 100644 score/mw/crypto/api/common/src/crypto_resource_guard_factory.hpp create mode 100644 score/mw/crypto/api/common/src/error_domain.cpp create mode 100644 score/mw/crypto/api/common/src/i_release_callback.hpp create mode 100644 score/mw/crypto/api/common/types.hpp create mode 100644 score/mw/crypto/api/config/BUILD create mode 100644 score/mw/crypto/api/config/base_context_config.hpp create mode 100644 score/mw/crypto/api/config/hash_context_config.hpp create mode 100644 score/mw/crypto/api/config/key_management_context_config.hpp create mode 100644 score/mw/crypto/api/config/key_operation_params.hpp create mode 100644 score/mw/crypto/api/config/mac_context_config.hpp create mode 100644 score/mw/crypto/api/config/permission_builder.hpp create mode 100644 score/mw/crypto/api/contexts/BUILD create mode 100644 score/mw/crypto/api/contexts/i_context.hpp create mode 100644 score/mw/crypto/api/contexts/i_hash_context.hpp create mode 100644 score/mw/crypto/api/contexts/i_key_management_context.hpp create mode 100644 score/mw/crypto/api/contexts/i_mac_context.hpp create mode 100644 score/mw/crypto/api/contexts/i_streaming_context.hpp create mode 100644 score/mw/crypto/api/contexts/i_streaming_output_context.hpp create mode 100644 score/mw/crypto/api/contexts/src/hash_context_impl.cpp create mode 100644 score/mw/crypto/api/contexts/src/hash_context_impl.hpp create mode 100644 score/mw/crypto/api/contexts/src/key_management_context_impl.cpp create mode 100644 score/mw/crypto/api/contexts/src/key_management_context_impl.hpp create mode 100644 score/mw/crypto/api/contexts/src/mac_context_impl.cpp create mode 100644 score/mw/crypto/api/contexts/src/mac_context_impl.hpp create mode 100644 score/mw/crypto/api/crypto_stack_factory.hpp create mode 100644 score/mw/crypto/api/future/certificate/BUILD create mode 100644 score/mw/crypto/api/future/certificate/cert_types.hpp create mode 100644 score/mw/crypto/api/future/certificate/i_csr_export.hpp create mode 100644 score/mw/crypto/api/future/certificate/i_ocsp_request_export.hpp create mode 100644 score/mw/crypto/api/future/common/BUILD create mode 100644 score/mw/crypto/api/future/common/i_secure_storage_manager.hpp create mode 100644 score/mw/crypto/api/future/config/BUILD create mode 100644 score/mw/crypto/api/future/config/aead_context_config.hpp create mode 100644 score/mw/crypto/api/future/config/certificate_context_config.hpp create mode 100644 score/mw/crypto/api/future/config/certificate_verification_context_config.hpp create mode 100644 score/mw/crypto/api/future/config/cipher_context_config.hpp create mode 100644 score/mw/crypto/api/future/config/csr_generation_context_config.hpp create mode 100644 score/mw/crypto/api/future/config/random_context_config.hpp create mode 100644 score/mw/crypto/api/future/config/sign_context_config.hpp create mode 100644 score/mw/crypto/api/future/config/verify_signature_context_config.hpp create mode 100644 score/mw/crypto/api/future/contexts/BUILD create mode 100644 score/mw/crypto/api/future/contexts/i_aead_context.hpp create mode 100644 score/mw/crypto/api/future/contexts/i_certificate_management_context.hpp create mode 100644 score/mw/crypto/api/future/contexts/i_certificate_verification_context.hpp create mode 100644 score/mw/crypto/api/future/contexts/i_cipher_context.hpp create mode 100644 score/mw/crypto/api/future/contexts/i_csr_generation_context.hpp create mode 100644 score/mw/crypto/api/future/contexts/i_random_context.hpp create mode 100644 score/mw/crypto/api/future/contexts/i_sign_context.hpp create mode 100644 score/mw/crypto/api/future/contexts/i_verify_signature_context.hpp create mode 100644 score/mw/crypto/api/future/contexts/src/i_certificate_context_impl_guide.hpp create mode 100644 score/mw/crypto/api/future/objects/BUILD create mode 100644 score/mw/crypto/api/future/objects/i_cert_slot_object.hpp create mode 100644 score/mw/crypto/api/future/objects/i_certificate_object.hpp create mode 100644 score/mw/crypto/api/future/objects/i_data_object.hpp create mode 100644 score/mw/crypto/api/future/objects/i_provider_object.hpp create mode 100644 score/mw/crypto/api/future/objects/i_secure_object.hpp create mode 100644 score/mw/crypto/api/i_crypto_context.hpp create mode 100644 score/mw/crypto/api/i_crypto_stack.hpp create mode 100644 score/mw/crypto/api/objects/BUILD create mode 100644 score/mw/crypto/api/objects/i_crypto_object.hpp create mode 100644 score/mw/crypto/api/objects/i_key_object.hpp create mode 100644 score/mw/crypto/api/objects/i_key_slot_object.hpp create mode 100644 score/mw/crypto/api/objects/i_private_key_object.hpp create mode 100644 score/mw/crypto/api/objects/i_public_key_object.hpp create mode 100644 score/mw/crypto/api/objects/i_symmetric_key_object.hpp create mode 100644 score/mw/crypto/api/src/crypto_context_impl.cpp create mode 100644 score/mw/crypto/api/src/crypto_context_impl.hpp create mode 100644 score/mw/crypto/api/src/crypto_stack_factory.cpp create mode 100644 score/mw/crypto/api/src/crypto_stack_impl.cpp create mode 100644 score/mw/crypto/api/src/crypto_stack_impl.hpp create mode 100644 score/mw/crypto/api/src/provider_type_converter.hpp delete mode 100644 src/BUILD create mode 100644 tests/README.md delete mode 100644 tests/cpp/BUILD delete mode 100644 tests/cpp/test_main.cpp create mode 100644 tests/demo/BUILD create mode 100644 tests/demo/mac_multi_provider_demo.cpp create mode 100644 tests/grpc_control_plane/BUILD create mode 100644 tests/grpc_control_plane/test_control_plane.cpp create mode 100644 tests/integration_tests/BUILD create mode 100644 tests/integration_tests/DEMO.md create mode 100644 tests/integration_tests/control_client_app.cpp create mode 100644 tests/integration_tests/init_softhsm_token.cpp create mode 100644 tests/integration_tests/integration_test.py create mode 100644 tests/integration_tests/score_api_hash_example.cpp create mode 100644 tests/integration_tests/score_api_mac_example.cpp create mode 100644 tests/integration_tests/score_demo.cpp create mode 100644 tests/key_management/BUILD create mode 100644 tests/key_management/config/BUILD create mode 100644 tests/key_management/config/key_management_test_config.json create mode 100644 tests/key_management/test_access_policy_enforcer.cpp create mode 100644 tests/key_management/test_key_config_manager.cpp create mode 100644 tests/key_management/test_key_management_context.cpp create mode 100644 tests/key_management/test_openssl_key_handler.cpp create mode 100644 tests/key_management/test_pkcs11_key_handler.cpp create mode 100644 tests/openssl/block_cipher/ECB-AES128/BUILD create mode 100644 tests/openssl/block_cipher/ECB-AES128/test_ecb_aes128.cpp create mode 100644 tests/provider_test/BUILD create mode 100644 tests/provider_test/test_pkcs11_multi_token.cpp create mode 100644 tests/provider_test/test_pkcs11_provider.cpp create mode 100644 tests/provider_test/test_provider.cpp create mode 100644 tests/provider_test/test_provider_manager.cpp delete mode 100644 tests/rust/BUILD delete mode 100644 tests/rust/test_main.rs create mode 100644 tests/softhsm/BUILD create mode 100644 tests/softhsm/block_cipher/ECB-AES128/BUILD create mode 100644 tests/softhsm/block_cipher/ECB-AES128/test_softhsm_ecb_aes128.cpp create mode 100644 tests/softhsm/softhsm_test_fixture.hpp create mode 100644 tests/test_vectors/block_cipher/ECB-AES128/BUILD create mode 100644 tests/test_vectors/block_cipher/ECB-AES128/block1.bin create mode 100644 tests/test_vectors/block_cipher/ECB-AES128/block1.enc create mode 100644 tests/test_vectors/block_cipher/ECB-AES128/block2.bin create mode 100644 tests/test_vectors/block_cipher/ECB-AES128/block2.enc create mode 100644 tests/test_vectors/block_cipher/ECB-AES128/block3.bin create mode 100644 tests/test_vectors/block_cipher/ECB-AES128/block3.enc create mode 100644 tests/test_vectors/block_cipher/ECB-AES128/block4.bin create mode 100644 tests/test_vectors/block_cipher/ECB-AES128/block4.enc create mode 100644 tests/test_vectors/block_cipher/ECB-AES128/key.bin create mode 100644 tests/test_vectors/block_cipher/ECB-AES128/reference.md create mode 100644 tests/test_vectors/config/BUILD create mode 100644 tests/test_vectors/config/integration_openssl_hmac.kv create mode 100644 tests/test_vectors/config/integration_softhsm_hmac.kv create mode 100644 tests/test_vectors/config/integration_test_config.json create mode 100644 tests/test_vectors/hash/BUILD create mode 100644 tests/test_vectors/hash/input_complete_data.bin create mode 100644 tests/test_vectors/hash/input_hello_world.bin create mode 100644 tests/test_vectors/hash/reference.md create mode 100644 tests/test_vectors/hash/sha256_complete_data.bin create mode 100644 tests/test_vectors/hash/sha256_hello_world.bin create mode 100644 tests/test_vectors/hash/sha384_hello_world.bin create mode 100644 tests/test_vectors/key_management/BUILD create mode 100644 tests/test_vectors/key_management/aes256_cmac.key create mode 100644 tests/test_vectors/key_management/hmac_sha256.key create mode 100644 tests/test_vectors/mac/BUILD create mode 100644 tests/test_vectors/mac/hmac_sha256_complete_data.bin create mode 100644 tests/test_vectors/mac/hmac_sha256_hello_world.bin create mode 100644 tests/test_vectors/mac/hmac_sha256_hi_there.bin create mode 100644 tests/test_vectors/mac/input_complete_data.bin create mode 100644 tests/test_vectors/mac/input_hello_world.bin create mode 100644 tests/test_vectors/mac/input_hi_there.bin create mode 100644 tests/test_vectors/mac/key_aes_256.key create mode 100644 tests/test_vectors/mac/reference.md create mode 100644 tests/utility/BUILD create mode 100644 tests/utility/test_utility.cpp create mode 100644 tests/utility/test_utility.hpp create mode 100644 third_party/grpc/BUILD create mode 100644 third_party/grpc/abseil_qnx_pthread.patch create mode 100644 third_party/grpc/grpc_qnx_pthread.patch create mode 100644 third_party/grpc/grpc_symbol_reference.cpp create mode 100644 third_party/grpc/protobuf_qnx_pthread.patch create mode 100644 third_party/openssl/BUILD create mode 100644 third_party/soft_hsm/BUILD create mode 100644 third_party/soft_hsm/softhsm_qnx_dl.patch diff --git a/.bazelrc b/.bazelrc index 51cac5a..8fe6fec 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,9 +1,103 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +# Import user-specific configurations +try-import %workspace%/user.bazelrc + build --java_language_version=17 build --tool_java_language_version=17 build --java_runtime_version=remotejdk_17 build --tool_java_runtime_version=remotejdk_17 +# Only contact license server every 59 secs per tool and not for every tool invocation to avoid flooding the license server +common --action_env=QNX_LICENSE_EXTSERVER_DELAY=59 +#common --action_env=QNX_LICENSE_QUEUE_TIMEOUT=300 # If not reach retry in 60seconds for overall 300 (configured) seconds +# Disable for the moment as this leads to "warning: Cannot find Build server key, the value of QNX_LICENSE_QUEUE_TIMEOUT will be ignored." + test --test_output=errors +# Bazel registry configuration common --registry=https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/ common --registry=https://bcr.bazel.build +# QNX credential helper configuration +common --credential_helper=*.qnx.com=%workspace%/.github/tools/qnx_credential_helper.py +common --credential_helper_timeout="60s" + +# Shared configuration for simple test execution +build:shared --incompatible_strict_action_env +build:shared --sandbox_writable_path=/var/tmp +build:shared --host_platform=@score_bazel_platforms//:x86_64-linux + +# Shared qnx configuration +build:shared_qnx --config=shared +build:shared_qnx --copt=-D_QNX_SOURCE +build:shared_qnx --copt=-DGRPC_POSIX_SOCKET +build:shared_qnx --copt=-DGRPC_POSIX_SOCKETUTILS +build:shared_qnx --copt=-DGRPC_POSIX_WAKEUP_FD +build:shared_qnx --copt=-DIP_PKTINFO=0 +build:shared_qnx --cxxopt=-D_QNX_SOURCE +build:shared_qnx --cxxopt=-DGRPC_POSIX_SOCKET +build:shared_qnx --cxxopt=-DGRPC_POSIX_SOCKETUTILS +build:shared_qnx --cxxopt=-DGRPC_POSIX_WAKEUP_FD +build:shared_qnx --cxxopt=-DIP_PKTINFO=0 + +# ------------------------------------------------------------------------------- +# Config dedicated to host platform CPU:x86_64 and OS:Linux +# ------------------------------------------------------------------------------- +build:x86_64-linux --config=shared +build:x86_64-linux --platforms=@score_bazel_platforms//:x86_64-linux-gcc_12.2.0-posix + +# ------------------------------------------------------------------------------- +# Different toolchain configuration for x86_64-linux +# ------------------------------------------------------------------------------- +build:host_config_1 --config=x86_64-linux +build:host_config_1 --extra_toolchains=@score_gcc_x86_64_toolchain//:x86_64-linux-gcc_12.2.0 +build:host_config_1 --features=use_pthread + +# ------------------------------------------------------------------------------- +# Config dedicated to target platform CPU:aarch64 and OS:linux +# ------------------------------------------------------------------------------- +build:aarch64-linux --config=shared +build:aarch64-linux --platforms=@score_bazel_platforms//:aarch64-linux-gcc_12.2.0-posix + +# ------------------------------------------------------------------------------- +# Different toolchain configuration for aarch64-linux +# ------------------------------------------------------------------------------- +build:target_config_3 --config=aarch64-linux +build:target_config_3 --extra_toolchains=@score_gcc_aarch64_toolchain//:aarch64-linux-gcc_12.2.0 + + +# ------------------------------------------------------------------------------- +# Config dedicated to target platform CPU:x86_64 and OS:QNX +# ------------------------------------------------------------------------------- +build:x86_64-qnx --config=shared_qnx +# Use custom platform that extends score platform with @platforms//os:qnx constraint +build:x86_64-qnx --platforms=//platforms:x86_64-qnx-extended + +# ------------------------------------------------------------------------------- +# Different toolchain configuration for x86_64-qnx +# ------------------------------------------------------------------------------- +build:target_config_1 --config=x86_64-qnx +build:target_config_1 --extra_toolchains=@score_qcc_x86_64_toolchain//:x86_64-qnx-sdp_8.0.0 + +# ------------------------------------------------------------------------------- +# Config dedicated to target platform CPU:aarch64 and OS:QNX +# ------------------------------------------------------------------------------- +build:aarch64-qnx --config=shared_qnx +# Use custom platform that extends score platform with @platforms//os:qnx constraint +build:aarch64-qnx --platforms=//platforms:aarch64-qnx-extended + +# ------------------------------------------------------------------------------- +# Different toolchain configuration for aarch64-qnx +# ------------------------------------------------------------------------------- +build:target_config_2 --config=aarch64-qnx +build:target_config_2 --extra_toolchains=@score_qcc_aarch64_toolchain//:aarch64-qnx-sdp_8.0.0 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..61b376d --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +{ + /// This devcontainer adds some ETAS-specific customizations to the S-CORE base devcontainer + /// Such as: + /// - CA Certificates + + "name": "eclipse-s-core-etas", + // Use the same image as the project's devcontainer + "image": "ghcr.io/eclipse-score/devcontainer:latest", + + // Initialize bazel cache on host + "initializeCommand": "mkdir -p ${localEnv:HOME}/.cache/bazel", + + // Mount ca-certificates from host + "mounts": [ + { + "source": "/usr/local/share/ca-certificates/", + "target": "/usr/local/share/ca-certificates/", + "type": "bind" + }, + { + "source": "${localEnv:HOME}/.qnx/license", + "target": "/opt/score_qnx/license", + "type": "bind" + } + ], + // Add your personal customizations + "onCreateCommand": { + "update certificates & install acl library": "sudo apt update && sudo apt install -y --no-install-recommends ca-certificates-java openjdk-17-jre-headless libacl1-dev && sudo update-ca-certificates", + "bazel use system trust store": "echo 'startup --host_jvm_args=-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts --host_jvm_args=-Djavax.net.ssl.trustStorePassword=changeit' | sudo tee --append /etc/bazel.bazelrc" + } +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index ff063c4..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,15 +0,0 @@ -# Comment out as not in score yet -# * @eclipse-score/infrastructure-tooling-community -# .* @eclipse-score/infrastructure-tooling-community -# .github/CODEOWNERS @eclipse-score/technical-lead - -# in separate repositories: -# -# /docs @eclipse-score/process-community -# /docs/manual @eclipse-score//safety-manager -# /docs/release @eclipse-score//quality-manager @eclipse-score//module-lead -# /docs/safety_plan @eclipse-score//safety-manager @eclipse-score//module-lead -# /docs/safety_analysis @eclipse-score//safety-manager -# /docs/verification @eclipse-score//quality-manager @eclipse-score//safety-manager -# /components @eclipse-score//technical-lead -# /components/*/ @eclipse-score//automotive-score-committers diff --git a/.github/PULL_REQUEST_TEMPLATE/bug_fix.md b/.github/PULL_REQUEST_TEMPLATE/bug_fix.md deleted file mode 100644 index 8341f51..0000000 --- a/.github/PULL_REQUEST_TEMPLATE/bug_fix.md +++ /dev/null @@ -1,19 +0,0 @@ -# Bugfix - -> [!IMPORTANT] -> Use this template only for bugfixes that do not influence topics covered by contribution requests or improvements. - -> [!CAUTION] -> Make sure to submit your pull-request as **Draft** until you are ready to have it reviewed by the Committers. - -## Description - -[A short description of the bug being fixed by the contribution.] - -## Related ticket - -> [!IMPORTANT] -> Please replace `[ISSUE-NUMBER]` with the issue-number that tracks this bug fix. If there is no such -> ticket yet, create one via [this issue template](../ISSUE_TEMPLATE/new?template=bug_fix.md). - -closes [ISSUE-NUMBER] (bugfix ticket) diff --git a/.github/PULL_REQUEST_TEMPLATE/improvement.md b/.github/PULL_REQUEST_TEMPLATE/improvement.md deleted file mode 100644 index 090ad43..0000000 --- a/.github/PULL_REQUEST_TEMPLATE/improvement.md +++ /dev/null @@ -1,19 +0,0 @@ -# Improvement - -> [!IMPORTANT] -> Use this template only for improvement that do not influence topics covered by contribution requests or bug fixes. - -> [!CAUTION] -> Make sure to submit your pull-request as **Draft** until you are ready to have it reviewed by the Committers. - -## Description - -[A short description of the improvement being addressed by the contribution.] - -## Related ticket - -> [!IMPORTANT] -> Please replace `[ISSUE-NUMBER]` with the issue-number that tracks this bug fix. If there is no such -> ticket yet, create one via [this issue template](../ISSUE_TEMPLATE/new?template=improvement.md). - -closes [ISSUE-NUMBER] (improvement ticket) diff --git a/.github/actions/gitlint/action.yml b/.github/actions/gitlint/action.yml deleted file mode 100644 index cb16e6f..0000000 --- a/.github/actions/gitlint/action.yml +++ /dev/null @@ -1,44 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2024 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -name: "Gitlint Action" -description: "An action to install and run Gitlint on PR commits" -inputs: - pr-number: - description: "Pull Request number used to fetch commits" - required: true - base-branch: - description: "Base branch to compare commits against (default: origin/main)" - default: "origin/main" - required: false -runs: - using: "docker" - image: "jorisroovers/gitlint:0.19.1" - entrypoint: /bin/sh - args: - - -c - - | - git config --global --add safe.directory /github/workspace && \ - git fetch origin +refs/heads/main:refs/remotes/origin/main && \ - git fetch origin +refs/pull/${{ inputs.pr-number }}/head && \ - # Fetch the upstream .gitlint config from eclipse-score/score - git clone --depth 1 https://github.com/eclipse-score/score.git /tmp/score-gitlint && \ - if ! gitlint --config /tmp/score-gitlint/.gitlint --commits origin/main..HEAD; then \ - echo -e "\nWARNING: Your commit message does not follow the required format." && \ - echo "Formatting rules: https://eclipse-score.github.io/score/main/contribute/general/git.html#commit-message-format" && \ - echo -e "To fix your commit message, run:\n" && \ - echo " git commit --amend" && \ - echo "Then update your commit (fix gitlint warnings). Finally, force-push:" && \ - echo " git push --force-with-lease" && \ - exit 1; \ - fi diff --git a/.github/workflows/copyright.yml b/.github/workflows/copyright.yml deleted file mode 100644 index 08ef376..0000000 --- a/.github/workflows/copyright.yml +++ /dev/null @@ -1,24 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2024 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -name: Copyright checks -on: - pull_request: - types: [opened, reopened, synchronize] - merge_group: - types: [checks_requested] -jobs: - copyright-check: - uses: eclipse-score/cicd-workflows/.github/workflows/copyright.yml@main - with: - bazel-target: "run //:copyright.check" diff --git a/.github/workflows/docs-cleanup.yml b/.github/workflows/docs-cleanup.yml deleted file mode 100644 index cfa4ae2..0000000 --- a/.github/workflows/docs-cleanup.yml +++ /dev/null @@ -1,29 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -name: Documentation Cleanup - -permissions: - contents: write - pages: write - id-token: write - -on: - schedule: - - cron: '0 0 * * *' # Runs every day at midnight UTC - -jobs: - docs-cleanup: - uses: eclipse-score/cicd-workflows/.github/workflows/docs-cleanup.yml@main - secrets: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 24bd399..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,43 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -name: Documentation - -permissions: - contents: write - pages: write - pull-requests: write - id-token: write - -on: - pull_request_target: - types: [opened, reopened, synchronize] # Allows forks to trigger the docs build - push: - branches: - - main - merge_group: - types: [checks_requested] - -jobs: - build-docs: - uses: eclipse-score/cicd-workflows/.github/workflows/docs.yml@main - permissions: - contents: write - pages: write - pull-requests: write - id-token: write - - with: - # the bazel-target depends on your repo specific docs_targets configuration (e.g. "suffix") - bazel-target: "//:docs -- --github_user=${{ github.repository_owner }} --github_repo=${{ github.event.repository.name }}" - retention-days: 3 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml deleted file mode 100644 index 620d697..0000000 --- a/.github/workflows/format.yml +++ /dev/null @@ -1,26 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2024 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -name: Formatting checks - -on: - pull_request: - types: [opened, reopened, synchronize] - merge_group: - types: [checks_requested] - -jobs: - formatting-check: - uses: eclipse-score/cicd-workflows/.github/workflows/format.yml@main - with: - bazel-target: "test //:format.check" # optional, this is the default diff --git a/.github/workflows/gitlint.yml b/.github/workflows/gitlint.yml deleted file mode 100644 index 90487ed..0000000 --- a/.github/workflows/gitlint.yml +++ /dev/null @@ -1,31 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2024 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -name: Gitlint check -on: - pull_request: - types: [opened, synchronize, reopened] -jobs: - lint-commits: - name: check-commit-messages - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Run Gitlint Action - if: ${{ github.event_name == 'pull_request' }} - uses: ./.github/actions/gitlint - with: - pr-number: ${{ github.event.number }} diff --git a/.github/workflows/license_check.yml b/.github/workflows/license_check.yml deleted file mode 100644 index aba7f99..0000000 --- a/.github/workflows/license_check.yml +++ /dev/null @@ -1,32 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -name: License check preparation -on: - pull_request_target: - types: [opened, reopened, synchronize] - merge_group: - types: [checks_requested] - -permissions: - pull-requests: write - issues: write - - -jobs: - license-check: - uses: eclipse-score/cicd-workflows/.github/workflows/license-check.yml@main - with: - repo-url: "${{ github.server_url }}/${{ github.repository }}" - secrets: - dash-api-token: ${{ secrets.ECLIPSE_GITLAB_API_TOKEN }} diff --git a/.gitignore b/.gitignore index e7dc329..275691f 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,12 @@ styles/ .envrc # Python -.venv +.venv* __pycache__/ /.coverage +.python-version +path/to/venv/ + +# Language Server +compile_commands.json +.cache/ diff --git a/BUILD b/BUILD index 473b5d5..9393e47 100644 --- a/BUILD +++ b/BUILD @@ -1,47 +1,20 @@ -# ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. # -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= load("@score_docs_as_code//:docs.bzl", "docs") -load("@score_tooling//:defs.bzl", "copyright_checker", "dash_license_checker", "setup_starpls", "use_format_targets") -load("//:project_config.bzl", "PROJECT_CONFIG") - -setup_starpls( - name = "starpls_server", - visibility = ["//visibility:public"], -) - -copyright_checker( - name = "copyright", - srcs = [ - "src", - "tests", - "//:BUILD", - "//:MODULE.bazel", - ], - config = "@score_tooling//cr_checker/resources:config", - template = "@score_tooling//cr_checker/resources:templates", - visibility = ["//visibility:public"], -) - -dash_license_checker( - src = "//examples:cargo_lock", - file_type = "", # let it auto-detect based on project_config - project_config = PROJECT_CONFIG, - visibility = ["//visibility:public"], -) +load("@score_tooling//:defs.bzl", "use_format_targets") # Add target for formatting checks -use_format_targets() +# use_format_targets() docs( source_dir = "docs", diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md deleted file mode 100644 index dcc54e6..0000000 --- a/CONTRIBUTION.md +++ /dev/null @@ -1,35 +0,0 @@ -# Eclipse Safe Open Vehicle Core (SCORE) -The [Eclipse Safe Open Vehicle Core](https://projects.eclipse.org/projects/automotive.score) project aims to develop an open-source core stack for Software Defined Vehicles (SDVs), specifically targeting embedded high-performance Electronic Control Units (ECUs). -Please check the [documentation](https://eclipse-score.github.io) for more information. -The source code is hosted at [GitHub](https://github.com/eclipse-score). - -The communication mainly takes place via the [`score-dev` mailing list](https://accounts.eclipse.org/mailing-list/score-dev) and GitHub issues & pull requests (PR). And we have a chatroom for community discussions here [Eclipse SCORE chatroom](https://chat.eclipse.org/#/room/#automotive.score:matrix.eclipse.org). - -Please note that for the project the [Eclipse Foundation’s Terms of Use](https://www.eclipse.org/legal/terms-of-use/) apply. -In addition, you need to sign the [ECA](https://www.eclipse.org/legal/ECA.php) and the [DCO](https://www.eclipse.org/legal/dco/) to contribute to the project. - -## Contributing -### Getting the source code & building the project -Please refer to the [README.md](README.md) for further information. - -### Getting involved - -#### Setup Phase -This phase is part of the eclipse Incubation Phase and shall establish all the processes needed for a safe development of functions. Only after this phase it will be possible to contribute code to the project. As the development in this project is driven by requirements, the processes and needed infrastructure incl. tooling will be established based on non-functional Stakeholder_Requirements. During setup phase the contributions are Bug Fixes and Improvements (both on processes and infrastructure). - -#### Bug Fixes and Improvements -Improvements are adding/changing processes and infrastructure, bug fixes can be also on development work products like code. -In case you want to fix a bug or contribute an improvement, please perform the following steps: -1) Create a PR by using the corresponding template ([Bugfix PR template](.github/PULL_REQUEST_TEMPLATE/bug_fix.md) or [Improvement PR template](.github/PULL_REQUEST_TEMPLATE/improvement.md)). Please mark your PR as draft until it's ready for review by the Committers (see the [Eclipse Foundation Project Handbook](https://www.eclipse.org/projects/handbook/#contributing-committers) for more information on the role definitions). Improvements are requested by the definition or modification of [Stakeholder Requirements](docs/stakeholder_requirements) or [Tool Requirements](docs/tool_requirements) and may be implemented after acceptance/merge of the request by a second Improvement PR. The needed reviews are automatically triggered via the [CODEOWNERS](.github/CODEOWNERS) file in the repository. -2) Initiate content review by opening a corresponding issue for the PR when it is ready for review. Review of the PR and final merge into the project repository is in responsibility of the Committers. Use the [Bugfix Issue template](.github/ISSUE_TEMPLATE/bug_fix.md) or [Improvement Issue template](.github/ISSUE_TEMPLATE/improvement.md) for this. - -Please check here for our Git Commit Rules in the [Configuration_Tool_Guidelines](https://eclipse-score.github.io/score/process_description/guidelines/index.html). - -Please use the [Stakeholder and Tool Requirements Template](https://eclipse-score.github.io/score/process_description/templates/index.html) when defining these requirements. - -![Contribution guide workflow](./docs/_assets/contribution_guide.svg "Contribution guide workflow") - -#### Additional Information -Please note, that all Git commit messages must adhere the rules described in the [Eclipse Foundation Project Handbook](https://www.eclipse.org/projects/handbook/#resources-commit). - -Please find process descriptions here: [process description](https://eclipse-score.github.io/score/process_description/). diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f433b1a..0000000 --- a/LICENSE +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/MODULE.bazel b/MODULE.bazel index befae8a..3d6f1f2 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,66 +1,231 @@ -# ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. # -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= module( - name = "cpp_rust_template_repository", - version = "1.0", + name = "score_crypto", + version = "0.1.0", ) -bazel_dep(name = "rules_python", version = "1.4.1", dev_dependency = True) +bazel_dep(name = "rules_python", version = "1.5.4") -# Python 3.12: Required for testing infrastructure and code generation tools PYTHON_VERSION = "3.12" -python = use_extension( - "@rules_python//python/extensions:python.bzl", - "python", - dev_dependency = True, -) +python = use_extension("@rules_python//python/extensions:python.bzl", "python") python.toolchain( is_default = True, python_version = PYTHON_VERSION, ) use_repo(python) -# Add GoogleTest dependency -bazel_dep(name = "googletest", version = "1.17.0", dev_dependency = True) +# Foreign CC rules for building projects with external build systems (make, cmake, etc.) +bazel_dep(name = "rules_foreign_cc", version = "0.15.1") -# Rust rules for Bazel -bazel_dep(name = "rules_rust", version = "0.63.0") +# HTTP archive rule for downloading and extracting source code from the web +http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -# C/C++ rules for Bazel -bazel_dep(name = "rules_cc", version = "0.2.14") +# =============================================================================== +# T O O L C H A I N S +# =============================================================================== + +# ******************************************************************************* +# Constraint values for specifying platforms and toolchains +# ******************************************************************************* +bazel_dep(name = "platforms", version = "1.0.0") +bazel_dep(name = "score_bazel_platforms", version = "0.1.1") -# LLVM Toolchains Rules - host configuration -bazel_dep(name = "toolchains_llvm", version = "1.5.0", dev_dependency = True) +# ******************************************************************************* +# C++ Rules for Bazel +# ******************************************************************************* +bazel_dep(name = "rules_cc", version = "0.2.16") -llvm = use_extension( - "@toolchains_llvm//toolchain/extensions:llvm.bzl", - "llvm", +# ******************************************************************************* +# Set dependency to Bazel C/C++ Toolchain repository +# ******************************************************************************* +bazel_dep( + name = "score_bazel_cpp_toolchains", + version = "0.3.1", dev_dependency = True, ) -llvm.toolchain( - cxx_standard = {"": "c++17"}, - llvm_version = "19.1.0", + +# ******************************************************************************* +# Init GCC extention +# Legend: +# * CPU: Control Processor Unit +# * OS: Operating System +# * V: Version (GCC or SDP) +# * ES: Runtime-EcoSystem +# +# ******************************************************************************* +gcc = use_extension("@score_bazel_cpp_toolchains//extensions:gcc.bzl", "gcc", dev_dependency = True) + +# ******************************************************************************* +# Setting default GCC (CPU:x86_64|OS:Linux|V:12.2.0|ES:posix) +# ******************************************************************************* +gcc.toolchain( + name = "score_gcc_x86_64_toolchain", + target_cpu = "x86_64", + target_os = "linux", + use_default_package = True, + version = "12.2.0", +) + +# ******************************************************************************* +# Setting default GCC (CPU:aarch64|OS:Linux|V:12.2.0|ES:posix) +# ******************************************************************************* +gcc.toolchain( + name = "score_gcc_aarch64_toolchain", + target_cpu = "aarch64", + target_os = "linux", + use_default_package = True, + version = "12.2.0", +) + +# ******************************************************************************* +# Setting GCC (CPU:x86_64|OS:QNX|version(sdp):8.0.0|ES:posix) +# ******************************************************************************* +gcc.toolchain( + name = "score_qcc_x86_64_toolchain", + sdp_version = "8.0.0", + target_cpu = "x86_64", + target_os = "qnx", + use_default_package = True, + version = "12.2.0", ) -use_repo(llvm, "llvm_toolchain") -use_repo(llvm, "llvm_toolchain_llvm") -register_toolchains("@llvm_toolchain//:all") +# ******************************************************************************* +# Setting GCC (CPU:aarch64|OS:QNX|version(sdp):8.0.0|ES:posix) +# ******************************************************************************* +gcc.toolchain( + name = "score_qcc_aarch64_toolchain", + sdp_version = "8.0.0", + target_cpu = "aarch64", + target_os = "qnx", + use_default_package = True, + version = "12.2.0", +) +use_repo( + gcc, + "score_gcc_aarch64_toolchain", + "score_gcc_x86_64_toolchain", + "score_qcc_aarch64_toolchain", + "score_qcc_x86_64_toolchain", +) + +# =============================================================================== +# T H I R D - P A R T Y D E P E N D E N C I E S +# =============================================================================== + +# ******************************************************************************* +# gRPC +# Intended to be replaced with score_communication transporting a size limited +# FlatBuffers in the future +# ******************************************************************************* +bazel_dep(name = "grpc", version = "1.76.0.bcr.1") + +# Patch grpc and bundled abseil-cpp to not link -lpthread on QNX (pthread is built into libc on QNX) +archive_override( + module_name = "grpc", + patch_strip = 1, + patches = ["//third_party/grpc:grpc_qnx_pthread.patch"], + strip_prefix = "grpc-1.76.0", + urls = ["https://github.com/grpc/grpc/archive/refs/tags/v1.76.0.tar.gz"], +) + +# Abseil-cpp dependency (pulled by grpc) +bazel_dep(name = "abseil-cpp", version = "20250512.1") + +# Patch abseil-cpp to not link -lpthread on QNX (pthread is built into libc on QNX) +archive_override( + module_name = "abseil-cpp", + patch_strip = 1, + patches = ["//third_party/grpc:abseil_qnx_pthread.patch"], + strip_prefix = "abseil-cpp-20250512.1", + urls = ["https://github.com/abseil/abseil-cpp/archive/refs/tags/20250512.1.tar.gz"], +) + +# Protobuf dependency (pulled by grpc) +bazel_dep(name = "protobuf", version = "31.1") + +# Patch protobuf to not link -lpthread on QNX (pthread is built into libc on QNX) +archive_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//third_party/grpc:protobuf_qnx_pthread.patch"], + strip_prefix = "protobuf-31.1", + urls = ["https://github.com/protocolbuffers/protobuf/archive/refs/tags/v31.1.tar.gz"], +) + +single_version_override( + module_name = "rules_swift", + version = "3.1.2", # resolve dependency conflict of grpc and flatbuffers +) + +# FlatBuffers +bazel_dep(name = "flatbuffers", version = "25.2.10") + +# ******************************************************************************* +# OpenSSL +# ******************************************************************************* + +http_archive( + name = "openssl_source", + build_file_content = """ +filegroup( + name = "all", + srcs = glob(["**"]), + visibility = ["//visibility:public"], +) +""", + sha256 = "b1bfedcd5b289ff22aee87c9d600f515767ebf45f77168cb6d64f231f518a82e", + strip_prefix = "openssl-3.6.1", + urls = ["https://www.openssl.org/source/openssl-3.6.1.tar.gz"], +) + +# ******************************************************************************* +# SoftHSMv2 +# ******************************************************************************* + +http_archive( + name = "softhsm_source", + build_file_content = """ +filegroup( + name = "all", + srcs = glob(["**"]), + visibility = ["//visibility:public"], +) +""", + patch_args = ["-p1"], + patches = ["//third_party/soft_hsm:softhsm_qnx_dl.patch"], + sha256 = "be14a5820ec457eac5154462ffae51ba5d8a643f6760514d4b4b83a77be91573", + strip_prefix = "SoftHSMv2-2.7.0", + urls = ["https://github.com/softhsm/SoftHSMv2/archive/refs/tags/2.7.0.tar.gz"], +) + +# =============================================================================== +# S C O R E M O D U L E S +# =============================================================================== # tooling -bazel_dep(name = "score_tooling", version = "1.0.4", dev_dependency = True) -bazel_dep(name = "aspect_rules_lint", version = "1.10.2", dev_dependency = True) -bazel_dep(name = "buildifier_prebuilt", version = "8.2.0.2", dev_dependency = True) +bazel_dep(name = "score_tooling", version = "1.0.2") +bazel_dep(name = "aspect_rules_lint", version = "1.5.3") +bazel_dep(name = "buildifier_prebuilt", version = "8.2.0.2") #docs-as-code -bazel_dep(name = "score_docs_as_code", version = "2.3.0", dev_dependency = True) +bazel_dep(name = "score_docs_as_code", version = "2.2.0") + +# GoogleTest +bazel_dep(name = "googletest", version = "1.17.0") + +# s-core baselibs +bazel_dep(name = "score_baselibs", version = "0.1.3") + +# Integration testing +bazel_dep(name = "score_itf", version = "0.1.0") diff --git a/NOTICE b/NOTICE deleted file mode 100644 index 5d111d4..0000000 --- a/NOTICE +++ /dev/null @@ -1,32 +0,0 @@ -# Notices for Eclipse Safe Open Vehicle Core - -This content is produced and maintained by the Eclipse Safe Open Vehicle Core project. - - * Project home: https://projects.eclipse.org/projects/automotive.score - -## Trademarks - -Eclipse, and the Eclipse Logo are registered trademarks of the Eclipse Foundation. - -## Copyright - -All content is the property of the respective authors or their employers. -For more information regarding authorship of content, please consult the -listed source code repository logs. - -## Declared Project Licenses - -This program and the accompanying materials are made available under the terms -of the Apache License Version 2.0 which is available at -https://www.apache.org/licenses/LICENSE-2.0. - -SPDX-License-Identifier: Apache-2.0 - -## Cryptography - -Content may contain encryption software. The country in which you are currently -may have restrictions on the import, possession, and use, and/or re-export to -another country, of encryption software. BEFORE using any encryption software, -please check the country's laws, regulations and policies concerning the import, -possession, or use, and re-export of encryption software, to see if this is -permitted. diff --git a/README.md b/README.md index 1e8af75..e85572c 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,44 @@ It provides a **standardized project structure**, ensuring best practices for: | File/Folder | Description | | ----------------------------------- | ------------------------------------------------- | | `README.md` | Short description & build instructions | -| `src/` | Source files for the module | +| `score/` | Crypto component | | `tests/` | Unit tests (UT) and integration tests (IT) | | `examples/` | Example files used for guidance | +| `third_party/` | Build file for external dependencies (e.g. gRPC) | | `docs/` | Documentation (Doxygen for C++ / mdBook for Rust) | -| `.github/workflows/` | CI/CD pipelines | | `.vscode/` | Recommended VS Code settings | | `.bazelrc`, `MODULE.bazel`, `BUILD` | Bazel configuration & settings | | `project_config.bzl` | Project-specific metadata for Bazel macros | -| `LICENSE.md` | Licensing information | -| `CONTRIBUTION.md` | Contribution guidelines | + +### Score Folder Layout + +``` +score/ ↠Source code â—„ main +├── mw/crypto/ +│ └── api/ ↠[LIBRARY] +│ ├── common/ +│ ├── config/ ↠API config +│ ├── contexts/ ↠Crypto contexts +│ ├── objects/ ↠Key/cert objects +│ └── src/ ↠Entry point +│ +└── crypto/ + ├── api/ + │ └── control_plane/ ↠[LIB CTRL-PLANE] + │ + ├── ipc/ + │ └── grpc_adapter/ ↠[IPC — gRPC] + │ + └── daemon/ + ├── control_plane/ ↠[DAEMON CTRL-PLANE] + ├── mediator/ ↠[MEDIATOR] + ├── data_manager/ ↠[DATA MANAGER] + ├── key_management/ ↠[KEY MANAGEMENT] + ├── config/ ↠[CONFIG] + └── provider/ + ├── score_provider/ ↠[SW PROVIDER / OpenSSL] + └── pkcs11/ ↠[HW PROVIDER / PKCS#11] +``` --- @@ -47,27 +75,23 @@ cd YOUR_PROJECT To build all targets of the module the following command can be used: ```sh -bazel build //src/... -``` - -This command will instruct Bazel to build all targets that are under Bazel -package `src/`. The ideal solution is to provide single target that builds -artifacts, for example: - -```sh -bazel build //src/:release_artifacts +# host platform +bazel build //score/... +# linux ARM architecture +# check .bazelrc for available host (x86_64) and target (aarch64) configurations +bazel build //score/... --config=target_config_3 ``` -where `:release_artifacts` is filegroup target that collects all release -artifacts of the module. - -> NOTE: This is just proposal, the final decision is on module maintainer how -> the module code needs to be built. - ### 3ï¸âƒ£ Run Tests ```sh +# pre-requisite: pull ubuntu docker image (once) +docker pull ubuntu:24.04 + +# host platform bazel test //tests/... +# with detailed output and no caching +bazel test //tests/... --test_output=all --cache_test_results=no ``` --- @@ -86,6 +110,10 @@ The template integrates **tools and linters** from **centralized repositories** - A **centralized docs structure** is planned. +```sh +bazel run //:docs +``` + --- ## âš™ï¸ `project_config.bzl` @@ -112,3 +140,7 @@ PROJECT_CONFIG = { When used with macros like `dash_license_checker`, it allows dynamic selection of file types (e.g., `cargo`, `requirements`) based on the languages declared in `source_code`. + +# Use of genAI in this repository +The repository partially contains AI-generated code by using GitHub Copilot Business. +This notice needs to remain attached to any reproduction of this repository. diff --git a/THIRD_PARTY_NOTICES b/THIRD_PARTY_NOTICES new file mode 100644 index 0000000..95173b4 --- /dev/null +++ b/THIRD_PARTY_NOTICES @@ -0,0 +1,20 @@ +This product includes software developed by and for use in the OpenSSL Toolkit +(https://www.openssl.org/). +Copyright (c) 1998-2024 The OpenSSL Project Authors. All rights reserved. +Licensed under the Apache License, Version 2.0 +(https://www.apache.org/licenses/LICENSE-2.0). + +This product includes SoftHSMv2 (https://github.com/opendnssec/SoftHSMv2), +Copyright (c) 2010 .SE (The Internet Infrastructure Foundation) and SURFnet bv. +Licensed under the BSD 2-Clause License +(https://opensource.org/licenses/BSD-2-Clause). + +This product includes FlatBuffers (https://github.com/google/flatbuffers), +Copyright (c) 2014 Google LLC. +Licensed under the Apache License, Version 2.0 +(https://www.apache.org/licenses/LICENSE-2.0). + +This product includes gRPC (https://github.com/grpc/grpc), +Copyright (c) 2015 gRPC authors. +Licensed under the Apache License, Version 2.0 +(https://www.apache.org/licenses/LICENSE-2.0). diff --git a/docs/conf.py b/docs/conf.py index cf13475..ded393a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,9 +20,9 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = "Module Template Project" -project_url = "https://eclipse-score.github.io/module_template/" -project_prefix = "MODULE_TEMPLATE_" +project = "Crypto Project" +project_url = "https://eclipse-score.github.io/inc_security_crypto" +project_prefix = "CRYPTO_" author = "S-CORE" version = "0.1" diff --git a/docs/crypto/architecture/api_architecture.rst b/docs/crypto/architecture/api_architecture.rst new file mode 100644 index 0000000..9e3db4f --- /dev/null +++ b/docs/crypto/architecture/api_architecture.rst @@ -0,0 +1,60 @@ +.. + # ============================================================================= + # C O P Y R I G H T + # ----------------------------------------------------------------------------- + # Copyright (c) 2026 by ETAS GmbH. All rights reserved. + # + # The reproduction, distribution and utilization of this file as + # well as the communication of its contents to others without express + # authorization is prohibited. Offenders will be held liable for the + # payment of damages. All rights reserved in the event of the grant + # of a patent, utility model or design. + # ============================================================================= + +.. _api_class_architecture: + +API Architecture +================ + +Class Diagram +------------- + +The Score Crypto API architecture is illustrated by several focused UML diagrams: + +.. + TODO: These might be later generated from the real_arc_int and operations. + +.. uml:: api_overview.puml + :align: center + :caption: Score Crypto API — Stack, Factory, and Context Overview + :alt: UML diagram showing ICryptoStack, ICryptoContext, and context factory relationships. + +.. uml:: api_contexts.puml + :align: center + :caption: Score Crypto API — Operation Context Hierarchy + :alt: UML diagram showing operation context inheritance and relationships (ICipherContext, IAeadContext, etc.). + +.. uml:: api_certificate_contexts.puml + :align: center + :caption: Score Crypto API — Certificate Operation Contexts + :alt: UML diagram showing certificate, verification, and CSR context relationships. + +.. uml:: api_typed_objects.puml + :align: center + :caption: Score Crypto API — Typed Object Hierarchy + :alt: UML diagram showing typed object inheritance (IKeyObject, ICertificateObject, etc.). + +.. uml:: api_resource_management.puml + :align: center + :caption: Score Crypto API — Resource Management + :alt: UML diagram showing CryptoResourceGuard, CryptoResourceId, and daemon ref-counting lifecycle. + +.. uml:: configuration_objects.puml + :align: center + :caption: Score Crypto API — Configuration Object Hierarchy + :alt: UML diagram showing configuration struct relationships (CipherContextConfig, KeyGenConfig, etc.). + +.. uml:: key_configuration_params.puml + :align: center + :caption: Score Crypto API — Key Configuration Parameters + :alt: UML diagram showing key configuration parameter relationships (DeriveKeyParams, AgreeKeyParams, KdfParameters). diff --git a/docs/crypto/architecture/api_certificate_contexts.puml b/docs/crypto/architecture/api_certificate_contexts.puml new file mode 100644 index 0000000..c15c1d4 --- /dev/null +++ b/docs/crypto/architecture/api_certificate_contexts.puml @@ -0,0 +1,127 @@ +@startuml score_crypto_api_certificate_contexts + +title Score Crypto API — Certificate Operation Contexts + +skinparam packageStyle rectangle + +' ============================================================ +' Context Base (abbreviated for reference) +' ============================================================ +package "Context Base" { + interface IContext { + } + +' ============================================================ +' Certificate Management +' ============================================================ +package "Certificate Management" { + interface ICertificateManagementContext { + __ Parsing __ + + ParseCertificate(cert_data, format) : Result + + ParseCertificates(cert_data, format) : Result> + __ Persistence (copy semantics) __ + + SaveCertificate(id, slot) : Result + __ Export __ + + GetCertificateExportSize(cert) : Result + + ExportCertificate(cert, format, output) : Result + + GetConvertedCertificateSize(cert, out_fmt) : Result + + ConvertCertificateFormat(input, in_fmt, out_fmt, output) : Result + __ Slot Management __ + + ClearCertificate(slot) : Result + + GetCertificateSlotInfo(slot) : Result + __ CRL Management __ + + ImportCrl(crl_data, format, issuer_cert, persist) : Result + + DeleteCrl(crl) : Result + + DeleteExpiredCrls() : Result + + DeleteExpiredCertificates() : Result + __ Key Extraction __ + + LoadCertificatePublicKey(cert) : Result> + __ OCSP __ + + GetOcspRequestData(cert, issuer_cert) : Result + } + + ICertificateManagementContext --|> IContext : inherits +} + +' ============================================================ +' Certificate Verification +' ============================================================ +package "Certificate Verification" { + interface ICertificateVerificationContext { + __ Configuration __ + + SetCertificate(cert) : Result + + SetCertificateChain(chain) : Result + + SetVerificationTrustStore(trust_store) : Result + + SetAdditionalTrustAnchors(anchors : span) : Result + + SetOcspResponse(response_data) : Result + + SetCrl(crl) : Result + + SetVerificationTime(epoch_seconds) : Result + + SetRevocationCheckPolicy(policy) : Result + __ Execution __ + + Verify() : Result + } + + ICertificateVerificationContext --|> IContext : inherits +} + +' ============================================================ +' CSR Generation +' ============================================================ +package "CSR Generation" { + interface ICsrGenerationContext { + __ Configuration __ + + SetSubjectKey(key) : Result + + SetSignatureAlgorithm(algorithm) : Result + + SetSubjectDn(dn) : Result + + AddSubjectAltName(san) : Result + __ Execution __ + + Generate() : Result + } + + ICsrGenerationContext --|> IContext : inherits +} + +' ============================================================ +' Configuration for Certificate Contexts +' ============================================================ +package "Configuration" { + class BaseContextConfig <> { + + algorithm : AlgorithmId + + provider : optional + + provider_type : optional + + operation_timeout : optional + + timeout_enabled : bool + } + + class CertificateContextConfig <> { + + SetProvider(prov) : CertificateContextConfig& + + SetProviderType(type) : CertificateContextConfig& + } + + class CertificateVerificationContextConfig <> { + + revocation_policy : optional + --- + + SetRevocationPolicy(policy) : CertificateVerificationContextConfig& + + SetProvider(prov) : CertificateVerificationContextConfig& + + SetProviderType(type) : CertificateVerificationContextConfig& + } + + class CsrGenerationContextConfig <> { + + SetAlgorithm(alg) : CsrGenerationContextConfig& + + SetProvider(prov) : CsrGenerationContextConfig& + + SetProviderType(type) : CsrGenerationContextConfig& + } + + CertificateContextConfig --|> BaseContextConfig : inherits + CertificateVerificationContextConfig --|> BaseContextConfig : inherits + CsrGenerationContextConfig --|> BaseContextConfig : inherits +} + +' ============================================================ +' Relationships +' ============================================================ +CertificateContextConfig ..> ICertificateManagementContext : configures +CertificateVerificationContextConfig ..> ICertificateVerificationContext : configures +CsrGenerationContextConfig ..> ICsrGenerationContext : configures + +@enduml diff --git a/docs/crypto/architecture/api_contexts.puml b/docs/crypto/architecture/api_contexts.puml new file mode 100644 index 0000000..a0388fd --- /dev/null +++ b/docs/crypto/architecture/api_contexts.puml @@ -0,0 +1,145 @@ +@startuml score_crypto_api_contexts + +title Score Crypto API — Crypto Operation Contexts + +skinparam packageStyle rectangle + +' ============================================================ +' Context Base Hierarchy +' ============================================================ +package "Context Base Hierarchy" { + interface IContext { + } + + interface IStreamingContext { + # Init(iv : optional> = nullopt) : Result + # Update(data : span) : Result + # Reset() : Result + } + + interface IStreamingOutputContext { + # Finalize(output : span) : Result + # GetOutputSize() : size_t + } + + IStreamingContext --|> IContext : inherits + IStreamingOutputContext --|> IStreamingContext : inherits +} + +' ============================================================ +' Hash & MAC +' ============================================================ +package "Hash & MAC" { + interface IHashContext { + + {using} Init(), Update(), Reset(), Finalize() + + SingleShot(input, output) : Result + + GetDigestSize() : size_t + ' Note: GetOutputSize() not exposed — use GetDigestSize() + } + + interface IMacContext { + + {using} Init(iv), Update(), Reset(), Finalize() + + Verify(mac : span) : Result + + GetMacSize() : size_t + ' Note: GetOutputSize() not exposed — use GetMacSize() + } + + IHashContext --|> IStreamingOutputContext : inherits + IMacContext --|> IStreamingOutputContext : inherits +} + +' ============================================================ +' Cipher & AEAD +' ============================================================ +package "Cipher & AEAD" { + interface ICipherContext { + + {using} Init(iv), Reset(), Finalize(), GetOutputSize() + + Update(input, output : span) : Result + + SingleShot(iv, input, output) : Result + ' Init(iv) from base: span implicitly converts to optional + ' IV mandatory for CBC/CTR/GCM; nullopt valid for ECB + ' direction set via CipherContextConfig::SetDirection() + } + + interface IAeadContext { + + {using} Init(iv), Reset() + + UpdateAad(aad : span) : Result + + Update(input, output : span) : Result + + Finalize(output, tag_out) : Result + + VerifyAndFinalize(output, tag_in) : Result + + GetTagSize() : size_t + ' Init(iv) from base: IV mandatory (nullopt returns error) + ' direction set via AeadContextConfig::SetDirection() + } + + ICipherContext --|> IStreamingOutputContext : inherits + IAeadContext --|> IStreamingContext : inherits +} + +' ============================================================ +' Signature +' ============================================================ +package "Signature" { + interface ISignContext { + + {using} Init(), Update(), Reset() + + SignFinalize(signature : span) : Result + + SingleShot(data, signature) : Result + + GetSignatureSize() : size_t + } + + interface IVerifySignatureContext { + + {using} Init(), Update(), Reset() + + VerifyFinalize(signature : span) : Result + + SingleShot(data, signature) : Result + } + + ISignContext --|> IStreamingOutputContext : inherits + IVerifySignatureContext --|> IStreamingContext : inherits +} + +' ============================================================ +' Key Management & Random +' ============================================================ +package "Key Management & Random" { + interface IKeyManagementContext { + __ Key Generation __ + + GenerateKey(params : GenerateKeyParams) : Result + + GenerateKey(target_slot, params : GenerateKeyParams) : Result + __ Key Derivation __ + + DeriveKey(params : DeriveKeyParams) : Result + + DeriveKey(target_slot, params : DeriveKeyParams) : Result + __ Key Agreement __ + + AgreeKey(params : AgreeKeyParams) : Result + + AgreeKey(target_slot, params : AgreeKeyParams) : Result + __ Persistence __ + + PersistKey(target_slot, ephemeral_key) : Result + + PersistKey(target_slot, guard) : Result + __ Key Unwrapping __ + + UnwrapKey(params : UnwrapKeyParams) : Result + + UnwrapKey(target_slot, params : UnwrapKeyParams) : Result + __ Key Import __ + + ImportKey(params : ImportKeyParams) : Result + + ImportKey(target_slot, params : ImportKeyParams) : Result + __ Key Loading __ + + LoadKey(slot) : Result + __ Key Wrapping & Export __ + + WrapKey(params : WrapKeyParams, output) : Result + + GetWrapKeySize(params : WrapKeyParams) : Result + + ExportKey(key, output, format) : Result + + GetExportKeySize(key, format) : Result + __ Key Clearing __ + + ClearKey(key) : Result + __ Slot Queries __ + + GetKeySlotInfo(slot) : Result + } + + interface IRandomContext { + + Generate(output : span) : Result + + Seed(seed : span) : Result + } + + IKeyManagementContext --|> IContext : inherits + IRandomContext --|> IContext : inherits +} + +@enduml diff --git a/docs/crypto/architecture/api_description.rst b/docs/crypto/architecture/api_description.rst new file mode 100644 index 0000000..c841a36 --- /dev/null +++ b/docs/crypto/architecture/api_description.rst @@ -0,0 +1,632 @@ +.. + # ============================================================================= + # C O P Y R I G H T + # ----------------------------------------------------------------------------- + # Copyright (c) 2026 by ETAS GmbH. All rights reserved. + # + # The reproduction, distribution and utilization of this file as + # well as the communication of its contents to others without express + # authorization is prohibited. Offenders will be held liable for the + # payment of damages. All rights reserved in the event of the grant + # of a patent, utility model or design. + # ============================================================================= + +.. _crypto_api_description: + +API Description +=============== + +.. + TODO: We should rather have an overview of the complete stack library and daemon architecture here. + Currently it is quite API focused. + +Resource ID Model +----------------- + +The API uses a two-phase resource identification model: + +1. **Resolution phase**: Applications call ``ICryptoContext::ResolveResource()`` + with an app-defined string ``ResourceId`` (e.g., ``"KeySlot_42"``) and the + expected ``ResourceType``. The daemon looks up the string in the per-application + configuration, verifies access control, and returns a ``CryptoResourceId``. + +2. **Operation phase**: All operation contexts, configs, and queries accept only + ``CryptoResourceId`` — no strings cross into operation contexts. + +``CryptoResourceId`` is a compact ~16-byte struct: + +.. code-block:: cpp + + struct CryptoResourceId { + uint64_t id; // daemon-assigned, unique per session + ResourceType type; // kProvider, kKeySlot, kCertSlot, kVerificationTrustStore, + // kKey, kCertificate, kCrl, kSecureObject, kDataObject + ResourcePersistence persistence; // kPersistent or kEphemeral + uint16_t primary_provider; // owning device/provider index (0 = unbound) + }; + +The struct is fully numeric, cheap to copy and hash, and includes +``operator==``, ``operator!=``, and ``std::hash`` specialization for use +in unordered containers. + +``CryptoResourceId`` unifies persistent and ephemeral resources via the +``ResourcePersistence`` field. Keys are ephemeral by default when +generated, derived, agreed, imported, or unwrapped — they receive a +``CryptoResourceId`` with ``type == kKey`` and +``persistence == kEphemeral``. Use +``IKeyManagementContext::PersistKey()`` to promote an ephemeral key +to a persistent slot. + +Key slots (``kKeySlot``) represent only logical persistent storage locations. +Ephemeral keys exist in transient memory and have no slot; some providers +may internally use RAM slots, but this is an implementation detail not +modeled at the interface level. + +For specialized queries on a resource (e.g., key algorithm, slot state, +certificate subject), typed object interfaces can be obtained from +``ICryptoContext`` accessor methods such as ``GetKeyObject()``, +``GetKeySlotObject()``, ``GetCertificateObject()``, and +``GetProviderObject()``. + +Resource Lifecycle and CryptoResourceGuard +------------------------------------------ + +Transient resources (ephemeral keys, loaded key material, extracted +certificate public keys) must be deterministically released to prevent +sensitive key material from lingering in daemon memory. + +All resource-producing methods (``GenerateKey``, ``DeriveKey``, ``AgreeKey``, +``UnwrapKey``, ``ImportKey``, ``LoadKey``, ``LoadCertificatePublicKey``, +``ImportCrl``) return ``CryptoResourceGuard`` — a move-only RAII wrapper. +Transient resource lifetime is managed by the daemon via per-resource reference +counting: the guard holds +a type-erased IPC release handle; ``Create*Context()`` atomically +validates the key and increments the daemon ref-count; guard destruction +decrements it. On client disconnect, the daemon bulk-frees all resources. + +The implicit conversion operator means a single +``SetKey(const CryptoResourceId&)`` signature works for both raw +``CryptoResourceId`` and ``CryptoResourceGuard`` — no overloads needed. + +**Two key usage paths:** + +1. **Slot-direct path** (simplest — no guard needed): + Pass a resolved ``kKeySlot`` directly to ``SetKey()``. The context + factory internally loads key material from the slot and releases it + on context destruction: + + .. code-block:: cpp + + auto slot = ctx->ResolveResource("MyKey", ResourceType::kKeySlot).value(); + CipherContextConfig config; + config.SetAlgorithm("AES-256-CBC").SetKey(slot).SetDirection(CipherDirection::kEncrypt); + auto cipher = ctx->CreateCipherContext(config).value(); + // Context loads key internally; releases on ~cipher. + +2. **Guard path** (for generated/loaded/derived/imported resources): + Resource-producing methods return a ``CryptoResourceGuard`` that + auto-releases on destruction: + + .. code-block:: cpp + + auto guard = key_mgmt->GenerateKey(GenerateKeyParams{}.SetAlgorithm("AES-256")).value(); + CipherContextConfig config; + config.SetAlgorithm("AES-256-GCM").SetKey(guard).SetDirection(CipherDirection::kEncrypt); + auto cipher = ctx->CreateCipherContext(config).value(); + // ... use cipher ... + // ~guard: Release(id) IPC → daemon decrements ref-count, frees ephemeral key. + +3. **Guard outliving its context** (guard destroyed after context): + After ``Create*Context()`` succeeds, the guard may be destroyed at any + time — the daemon bound the key to the context and incremented its + ref-count. The context continues to use the key independently: + + .. code-block:: cpp + + ICipherContext::Uptr cipher; + { + auto guard = key_mgmt->GenerateKey("AES-256").value(); + config.SetAlgorithm("AES-256-GCM").SetKey(guard).SetDirection(CipherDirection::kEncrypt); + cipher = ctx->CreateCipherContext(config).value(); + // Daemon ref-count = 2 (guard + context) + } // ~guard: Release(id) IPC → daemon ref-count = 1 (context holds ref) + cipher->Init(iv); + cipher->Update(plaintext, ciphertext); + cipher->Finalize(output); + // ~cipher: daemon ref-count = 0 → key freed + +**Cleanup ownership via ResourceType:** + +When a config's key has ``type == kKeySlot``, the context owns the +internally-loaded copy and releases it on destruction. When +``type == kKey``, the caller (guard) owns the material — the context +only references it. No communication between guard and context is +needed; the ``ResourceType`` already encodes the ownership model. + +**Cleanup guarantee:** + +1. ``CryptoResourceGuard`` destructor — automatic, deterministic. + Sends ``Release(id)`` IPC (daemon decrements ref-count). +2. Daemon bulk-free on process exit or crash — + all resources registered for the client are freed regardless of + whether destructors ran (crash-safe safety net). This is not + triggered by individual ``ICryptoStack`` destruction. + +**Explicit release with synchronous error handling**: + +When the application needs to explicitly confirm release before destruction — for example, +to detect that the resource is still referenced by an active context — call +``guard.Release()``: + +.. code-block:: cpp + + auto result = guard.Release(); // Result; auto-deactivates on success + if (!result.has_value()) { + // e.g. resource still used by an active context — destroy it first + } + +Memory and Data Plane Model +--------------------------- + +The data plane is architecturally independent of the IPC control plane: + +- ``ICryptoStack::GetMemoryAllocator()`` returns a ``Result`` + transferring ownership of the allocator to the caller. +- ``IMemoryAllocator::Allocate(size)`` allocates shared memory with ``kDefault`` + type. The daemon tracks allocations against a per-application quota + (configurable, overridable per app). +- ``IMemoryAllocator::Allocate(size, kProviderCompatible, providerHandle)`` + allocates memory directly usable by a specific provider (e.g., DMA-capable + for hardware/TEE), enabling the zero-copy path. +- ``IReadWriteMemoryRegion`` provides ``AsSpan()`` and ``AsWritableSpan()`` for + passing data to operation contexts. +- Memory regions are shared between library and daemon, so operation data does + not traverse the IPC serialization path. +- Destruction of a memory region releases it from the daemon's pool and adjusts + the quota. + +Zero-Copy Path +-------------- + +When provider-compatible memory is used, the data path avoids all copies: + +1. Application allocates provider-compatible memory via + ``Allocate(size, kProviderCompatible, providerHandle)`` +2. Application writes data into the region +3. Application passes ``region.AsSpan()`` to an operation context +4. Daemon forwards the same physical memory to the target provider +5. No copies occur end-to-end + +For non-compatible memory or when ``kDefault`` is used, the daemon copies +data into an internal provider-compatible buffer transparently. + +Base Class Hierarchy +-------------------- + +Three base interfaces capture shared behavior across operation contexts, +promoting DRY code reuse: + +.. code-block:: none + + IContext + ├── IStreamingContext (Init + Update) + │ ├── IStreamingOutputContext (+ Finalize + GetOutputSize) + │ │ ├── IHashContext (+ SingleShot, GetDigestSize) + │ │ ├── ICipherContext (+ Init with IV, SingleShot; direction from config) + │ │ ├── ISignContext (+ SignFinalize, SingleShot, GetSignatureSize) + │ │ └── IMacContext (+ Verify, GetMacSize) + │ ├── IAeadContext (+ UpdateAad, Finalize, VerifyAndFinalize, GetTagSize) + │ └── IVerifySignatureContext (+ VerifyFinalize, SingleShot) + ├── IRandomContext (Generate, Seed) + ├── IKeyManagementContext (key lifecycle operations) + ├── ICertificateManagementContext (certificate lifecycle operations) + ├── ICertificateVerificationContext (builder-style chain verification) + └── ICsrGenerationContext (builder-style CSR generation) + +Typed Object Hierarchy +^^^^^^^^^^^^^^^^^^^^^^ + +Typed crypto object interfaces provide specialized access to resources +identified by ``CryptoResourceId``. Objects are obtained via +``ICryptoContext`` accessor methods and are lightweight proxies into +daemon state, not owned data copies: + +.. code-block:: none + + ICryptoObject (base — GetId, GetType) + ├── IKeyObject (algorithm, persistence, exportability, key length) + │ ├── ISymmetricKeyObject (allowed cipher modes) + │ ├── IPublicKeyObject (ExportPublicKey) + │ └── IPrivateKeyObject (HasCorrespondingPublicKey) + ├── IKeySlotObject (slot state, allowed algorithm, provider binding) + ├── ICertificateObject (subject, issuer, validity, public key algorithm) + ├── ICertSlotObject (occupancy) + ├── IProviderObject (provider type, name, supported algorithms) + ├── ISecureObject (data, size) + └── IDataObject (data, size) + +- ``IContext``: Root base with ``Uptr`` typedef and virtual destructor +- ``IStreamingContext``: Adds ``Init() → Result``, + ``Update(span) → Result``, and + ``Reset() → Result`` for streaming patterns +- ``IStreamingOutputContext``: Adds ``Finalize(span) → Result`` + and ``GetOutputSize() → size_t`` for operations that produce output + +Contexts needing extra ``Init`` parameters (e.g., IV for encrypt/AEAD) hide +the base ``Init()`` with a more specific signature. + +Context Reuse via Reset() +^^^^^^^^^^^^^^^^^^^^^^^^^ + +All streaming contexts (hash, encrypt, decrypt, sign, verify, MAC, AEAD) +support in-place reuse through ``Reset()``, declared on +``IStreamingContext``: + +.. code-block:: cpp + + virtual Result Reset() = 0; + +``Reset()`` returns the context to its **post-construction** state, +clearing the streaming state machine and any accumulated intermediate +data while preserving: + +- The key binding established at context creation +- The algorithm and provider selection +- The configuration (including per-context timeout overrides) + +**Reuse lifecycle**:: + + Init() → Update()* → Finalize() → Reset() → Init() → ... + +**State-machine rules**: + +- ``Reset()`` is valid after ``Finalize()`` / ``SignFinalize()`` / + ``VerifyFinalize()`` / ``VerifyAndFinalize()``, or mid-stream + (aborting the current sequence). +- ``Reset()`` on an already-idle context (post-construction or + post-Reset) is a no-op that returns success. +- ``Reset()`` on a destroyed context returns + ``kContextAlreadyDestroyed``. +- On failure the context transitions to error state; the caller should + destroy and recreate the context via the factory. + +**Error code**: ``kContextResetFailed`` is returned +when the daemon cannot clear the context's internal state. + +Device Binding +-------------- + +``CryptoResourceId::primary_provider`` (``uint16_t``) identifies the owning +device/provider. This embeds device binding directly in the handle so any +code holding a reference knows which provider owns the resource without a +daemon round-trip. + +Access Control +-------------- + +``ICryptoContext::ResolveResource()`` enforces per-application ACL based on +uid. The same ``ResourceId`` string may resolve to different +``CryptoResourceId`` handles for different application instances. + +The library transparently passes caller identity to the daemon during +connection setup. The daemon's per-application configuration defines which +resources (key slots, certificate slots, trust anchors) each application +identity is permitted to access. + +Key Operation Permission Model +------------------------------ + +Beyond access control (which controls *who* may use a resource), the API +enforces **per-key operation permissions** — controlling *what* a key may +do. This implements the principle of least privilege: a key provisioned +for signing cannot be misused for encryption, and vice versa. + +**Permission bitmask:** + +``KeyOperationPermission`` is a ``uint32_t`` bitmask grouped by +operation category: + +.. code-block:: none + + Data protection (bits 0–3): kEncrypt, kDecrypt, kWrap, kUnwrap + Authentication (bits 4–7): kSign, kVerify, kMac, kAgree + Key lifecycle (bits 8–10): kDerive, kExport, kImport + +Composite presets are provided for common deployment patterns: + +- ``kDataProtection`` = encrypt + decrypt + wrap + unwrap +- ``kAuthentication`` = sign + verify + mac + agree +- ``kFullLifecycle`` = derive + export + import +- ``kAll`` = all operations permitted (default) +- ``kNone`` = storage-only key (no operations) + +**Permission sources:** + +1. **Slot-provisioned permissions**: Each key slot has a + ``permitted_operations`` field set during daemon-side provisioning. + When a key is loaded from a slot (either via ``LoadKey()`` or the + slot-direct path), the key inherits the slot's permissions. + +2. **Generation-time permissions**: ``GenerateKeyParams``, ``ImportKeyParams``, + and ``UnwrapKeyParams`` include a ``permissions`` field + (defaults to ``kAll``). This constrains the ephemeral key at + creation time. + +3. **Persist-time validation**: When an ephemeral key is persisted to + a slot via ``PersistKey()``, the daemon validates that the key's + permissions are a subset of the target slot's + ``permitted_operations``. If not, the persist fails with + ``kKeyOperationNotPermitted``. + +**Enforcement:** + +Permissions are enforced by the daemon at context creation time. Each +``Create[Op]Context()`` method maps to a required permission: + +.. code-block:: none + + CreateCipherContext(kEncrypt) → kEncrypt + CreateCipherContext(kDecrypt) → kDecrypt + CreateSignContext() → kSign + CreateVerifySignatureContext() → kVerify + CreateMacContext() → kMac + CreateAeadContext(kEncrypt) → kEncrypt + CreateAeadContext(kDecrypt) → kDecrypt + WrapKey() → kWrap (on wrapping key) + UnwrapKey() → kUnwrap (on wrapping key) + DeriveKey() → kDerive (on source key) + ExportKey() → kExport + AgreeKey() → kAgree + +If the key's permissions do not include the required operation, the +daemon returns ``CryptoErrorCode::kKeyOperationNotPermitted`` and the +context is not created. This is a fail-fast check — no resources are +allocated on permission violation. + +**Querying permissions:** + +Permissions can be queried at runtime via: + +- ``IKeySlotObject::GetPermittedOperations()`` — slot-level policy +- ``IKeyObject::GetPermittedOperations()`` — effective key permissions +- ``KeySlotInfo::permitted_operations`` field +- ``HasPermission(granted, required)`` — convenience predicate + +.. code-block:: cpp + + auto slot_obj = ctx->GetKeySlotObject(slot).value(); + auto perms = slot_obj->GetPermittedOperations(); + if (HasPermission(perms, KeyOperationPermission::kSign)) { + // This key can be used for signing + } + +Configuration Model +------------------- + +The system uses a two-tier configuration model: + +1. **Daemon configuration** (loaded at daemon startup): + + - Provider enumeration and hardware capability discovery + - Per-provider configuration (device paths, library paths) + - System-wide security policy + +2. **Per-application configuration** (loaded per connection): + + - Resource mappings (string → slot/provider bindings) + - Memory quotas (max allocation per application) + - Accessible key slots and certificate slots + - Trust anchor assignments + - Provider preferences + +``CreateCryptoStack(CryptoStackConfig)`` is the entry point; connection +management is internal. + +Config Extensibility Contract +----------------------------- + +All configuration structs (``CryptoStackConfig`` and all per-operation +context configs) follow the same backward-compatible extensibility pattern: + +- **Default-constructible** — no positional constructor arguments. All + construction via default constructor + fluent builder setters. +- **Fluent builder setters** (``Set...() → Config&``) are the only way to + populate fields — callers who don't call new setters get default behavior + automatically. +- **Adding new optional fields never breaks existing call sites** + (source-compatible). Existing code continues to compile and behave + identically. + +Provider Auto-Resolution +------------------------- + +When ``provider`` is omitted from a context config but a ``key`` is +specified, the daemon auto-resolves the provider from +``CryptoResourceId::primary_provider`` of the key. + +Certificate Lifecycle +--------------------- + +**ICertificateManagementContext** handles the full certificate lifecycle: + +- **Parsing**: ``ParseCertificate()`` returns an ``ICertificateObject::Uptr`` with + field accessors (subject, issuer, serial, validity dates, algorithm). The object + is backed by a daemon-assigned ephemeral ``CryptoResourceId``. +- **Persistence**: ``SaveCertificate(id, slot)`` promotes a parsed certificate to a slot + (copy semantics — the parsed object remains valid after the call) +- **Export**: ``GetCertificateExportSize()`` + ``ExportCertificate()`` two-call pattern +- **CRL**: ``ImportCrl()``, ``DeleteCrl()``, ``DeleteExpiredCrls()`` for offline revocation +- **Key extraction**: ``LoadCertificatePublicKey()`` extracts the public key + as a ``CryptoResourceGuard`` wrapping an ephemeral ``CryptoResourceId`` + with ``type == kKey``, following the same guard model as key-producing methods. + Use ``ICryptoContext::GetKeyObject()`` for specialized key property queries. +- **OCSP**: ``GetOcspRequestData()`` generates a request; the response is + consumed via ``ICertificateVerificationContext::SetOcspResponse()`` + +**ICertificateVerificationContext** provides builder-style chain verification: + +- ``SetCertificate()``, ``SetCertificateChain()``, ``SetVerificationTrustStore()``, ``SetAdditionalTrustAnchors()`` +- ``SetRevocationCheckPolicy()`` with CRL, OCSP, or combined strategies +- ``Verify()`` executes the configured verification + +**ICsrGenerationContext** provides builder-style CSR generation: + +- ``SetSubjectKey()``, ``SetSignatureAlgorithm()``, ``SetSubjectDn()`` +- ``AddSubjectAltName()`` for SAN extensions +- ``Generate()`` produces an ``ICsrExport`` with encoded CSR bytes + +IPC Transport +------------- + +The IPC transport (control plane serialization, encoding, protocol +negotiation) is an internal implementation concern **outside the scope of +this user-facing API**. The API layer defines only the logical interfaces; +the IPC layer will be addressed separately. + +Operation Timeout and Deadline +------------------------------ + +Every IPC call to the daemon is bounded by a configurable per-call +deadline to ensure deterministic behavior (ISO 26262 SA2) and graceful +degradation (SA5). + +**Two-layer timeout model:** + +1. **Stack-level default** via + ``CryptoStackConfig::SetDefaultOperationTimeout(ms)``: + Applies to *all* IPC calls on this stack — ``ResolveResource()``, + ``Create[Op]Context()``, ``Init()``, ``Update()``, ``Finalize()``, + ``QueryCapabilities()``, etc. When ``std::nullopt`` (default), the + daemon applies its own built-in default (implementation-defined, + typically 5000 ms). + +2. **Per-context override** via ``BaseContextConfig``: + ``SetOperationTimeout(ms)`` overrides the stack default for a + specific context. ``DisableTimeout()`` removes the deadline entirely + (the context waits indefinitely). ``EnableTimeout()`` re-enables it. + +**Effective timeout resolution:** + +.. code-block:: none + + if (config.timeout_enabled == false) + → no deadline (infinite wait) + else if (config.operation_timeout.has_value()) + → config.operation_timeout + else if (stack_config.default_operation_timeout.has_value()) + → stack_config.default_operation_timeout + else + → daemon built-in default + +**Timeout semantics:** + +- The timeout applies **per-IPC-call**, not per streaming sequence. + Each ``Init()``, ``Update()``, ``Finalize()`` has its own deadline. + For ``SingleShot()``, the timeout covers the single IPC call. +- On timeout, the operation returns + ``CryptoErrorCode::kOperationTimedOut`` and the context transitions + to an error state. Subsequent calls return ``kInvalidOperation``. + The context must be destroyed and recreated. +- Implementation must use ``std::chrono::steady_clock`` (monotonic) to + be immune to NTP and wall-clock adjustments. + +**Daemon-side enforcement (architectural constraint):** + +The deadline is **enforced by the daemon**, not by the client library. +This is an architectural requirement — not merely an implementation +detail — because only the daemon owns the resources that need cleanup +on timeout: + +1. **Deadline propagation**: The client library propagates the + resolved deadline to the daemon as IPC-level metadata (e.g., gRPC + deadline). The daemon receives and enforces it. + +2. **Daemon-side checks**: The daemon checks the deadline at key + decision points during operation processing: + + - Before dispatching to a crypto provider + - Between provider calls for multi-step operations + - Before allocating or modifying resources + + If the deadline has expired, the daemon aborts immediately without + performing the operation. + +3. **Resource cleanup on timeout**: When the daemon aborts due to + deadline expiration, it is responsible for: + + - Zeroing and releasing any intermediate key material + - Releasing provider handles and locks + - Reclaiming any allocated shared memory + - Transitioning the server-side context state to ``kError`` + + This is the same cleanup path as context destruction, ensuring no + resources are orphaned. + +4. **Client-side role**: The client library's only responsibility is + to propagate the deadline and interpret the daemon's timeout + response (``kOperationTimedOut``). The client does **not** perform + independent timer-based cancellation — the daemon is the single + source of truth. + +5. **IPC backend contract**: Any IPC transport used by this + architecture (gRPC, shared-memory IPC, SOME/IP) must support + deadline propagation from client to daemon. This is an + architectural constraint on the IPC layer. + +This daemon-side enforcement model provides a stronger safety guarantee +than client-side timeout alone: it ensures that even if the client +process crashes or is preempted after sending a request, the daemon will +still abort the operation after the deadline expires and clean up all +associated resources. + +**Disabling timeout:** + +Certain operations are legitimately long-running and should not be +interrupted: + +- PQC key generation on hardware tokens (ML-KEM, ML-DSA) +- HSM-backed key agreement or unwrapping +- Certificate chain verification with online OCSP + +For these, use ``config.DisableTimeout()`` on the relevant context +config. Document the rationale in the safety case when using +``DisableTimeout()`` in safety-relevant applications, as it removes +the WCET bound. + +.. code-block:: cpp + + // Stack-wide 500 ms deadline + CryptoStackConfig stack_config; + stack_config.SetConnectionEndpoint("unix:///var/run/crypto-daemon.sock") + .SetDefaultOperationTimeout(std::chrono::milliseconds{500}); + + // Per-context override: 200 ms for hash, disabled for key gen + HashContextConfig hash_cfg; + hash_cfg.SetAlgorithm("SHA-256") + .SetOperationTimeout(std::chrono::milliseconds{200}); + + KeyManagementContextConfig keygen_cfg; + keygen_cfg.DisableTimeout(); // PQC key generation may take seconds + +Rationale Behind Architecture Decomposition +-------------------------------------------- + +The architecture is decomposed along three axes: + +1. **Control plane vs. data plane**: Memory allocation + (``IMemoryAllocator``) is separated from the control plane + (``ICryptoContext``, operation contexts) because (a) it is + architecturally independent, (b) it can be passed independently to + components needing only memory, and (c) it enables isolated unit + testing of memory management. + +2. **Common types vs. operation contexts**: Common types, error domain, + and memory interfaces are in ``common/`` with no dependencies on + operation-specific code. This enables minimal-dependency compilation + units and independent unit testing. + +3. **Base hierarchy vs. concrete contexts**: Three base interfaces + (``IContext``, ``IStreamingContext``, ``IStreamingOutputContext``) + capture shared streaming behavior, while concrete contexts add + operation-specific methods. Configs are separated into ``config/`` + to decouple configuration from the interfaces they parameterize. diff --git a/docs/crypto/architecture/api_overview.puml b/docs/crypto/architecture/api_overview.puml new file mode 100644 index 0000000..dd0f02a --- /dev/null +++ b/docs/crypto/architecture/api_overview.puml @@ -0,0 +1,86 @@ +@startuml score_crypto_api_overview + +title Score Crypto API — Stack, Factory & Context Overview + +skinparam packageStyle rectangle + +' ============================================================ +' Factory +' ============================================================ +package "Factory" { + class CryptoStackConfig <> { + + connection_endpoint : string + + default_operation_timeout : optional + --- + + SetConnectionEndpoint(endpoint) : CryptoStackConfig& + + SetDefaultOperationTimeout(timeout) : CryptoStackConfig& + } + + interface "CreateCryptoStack" as CreateCryptoStack { + + CreateCryptoStack(config : CryptoStackConfig) : Result + } +} + +' ============================================================ +' Stack & Resource Ownership +' ============================================================ +package "Stack & Ownership" { + interface ICryptoStack { + + CreateCryptoContext() : Result + + GetMemoryAllocator() : Result + } + + interface IMemoryAllocator { + + Allocate(size) : Result + + Allocate(size, type, provider) : Result + + GetQuota() : size_t + + GetCurrentUsage() : size_t + } + +} + +' ============================================================ +' ICryptoContext +' ============================================================ +package "Crypto Context" { + interface ICryptoContext { + __ Resource Resolution __ + + ResolveResource(resource_id, type) : Result + __ Context Factory (Crypto) __ + + CreateHashContext(config) : Result + + CreateMacContext(config) : Result + + CreateKeyManagementContext(config) : Result + + CreateCipherContext(config) : Result + + CreateSignContext(config) : Result + + CreateVerifySignatureContext(config) : Result + + CreateAeadContext(config) : Result + + CreateRandomContext(config) : Result + __ Context Factory (Certificate) __ + + CreateCertificateManagementContext(config) : Result + + CreateCertificateVerificationContext(config) : Result + + CreateCsrGenerationContext(config) : Result + __ Queries __ + + QueryCapabilities(algorithm) : Result + + QueryCapabilities() : Result + + QueryProviderCompatibility(resource) : Result + + GetProviderInfo(provider_id) : Result + __ Typed Object Access __ + + GetKeyObject(id) : Result + + GetKeySlotObject(id) : Result + + GetCertificateObject(id) : Result + + GetCertSlotObject(id) : Result + + GetProviderObject(id) : Result + } + + ICryptoContext -[hidden]-> ICryptoStack +} + +' ============================================================ +' Relationships +' ============================================================ +CreateCryptoStack ..> CryptoStackConfig : accepts +CreateCryptoStack --> ICryptoStack : creates +ICryptoStack --> ICryptoContext : creates +ICryptoStack --> IMemoryAllocator : provides + +@enduml diff --git a/docs/crypto/architecture/api_resource_management.puml b/docs/crypto/architecture/api_resource_management.puml new file mode 100644 index 0000000..13cb79f --- /dev/null +++ b/docs/crypto/architecture/api_resource_management.puml @@ -0,0 +1,47 @@ +@startuml score_crypto_api_resource_management + +title Score Crypto API — Resource Management + +skinparam packageStyle rectangle + +' ============================================================ +' Resource Management +' ============================================================ +package "Resource Management" { + class CryptoResourceGuard { + - release_handle_ : shared_ptr // internal, type-erased + - id_ : CryptoResourceId + - active_ : bool + --- + + Id() : const CryptoResourceId& + + {implicit} operator const CryptoResourceId&() + + IsActive() : bool + + Release() : Result + } + + class CryptoResourceGuardFactory <> { + + {static} Make(handle, id) : CryptoResourceGuard // IPC layer only + } + + class CryptoResourceId <> { + + id : uint64_t + + type : ResourceType + + persistence : ResourcePersistence + + primary_provider : uint16_t + --- + + operator==(other) : bool + + operator!=(other) : bool + } + + note as N_Lifetime + Key lifetime managed by daemon ref-counting. + Guard holds a type-erased IPC release handle. + CryptoResourceGuardFactory is the sole + construction path — only IPC-layer implementations + can create guards. + end note + + CryptoResourceGuard --> CryptoResourceId : guards +} + +@enduml diff --git a/docs/crypto/architecture/api_typed_objects.puml b/docs/crypto/architecture/api_typed_objects.puml new file mode 100644 index 0000000..9e67513 --- /dev/null +++ b/docs/crypto/architecture/api_typed_objects.puml @@ -0,0 +1,104 @@ +@startuml score_crypto_api_typed_objects + +title Score Crypto API — Typed Object Hierarchy + +skinparam packageStyle rectangle + +' ============================================================ +' Root +' ============================================================ +interface ICryptoObject { + + GetId() : CryptoResourceId + + GetType() : ResourceType +} + +' ============================================================ +' Key Objects +' ============================================================ +package "Key Objects" { + interface IKeyObject { + + GetAlgorithm() : AlgorithmId + + GetPersistence() : ResourcePersistence + + IsExportable() : bool + + GetKeyLength() : size_t + + GetPermittedOperations() : KeyOperationPermission + } + + interface IPrivateKeyObject { + + HasCorrespondingPublicKey() : bool + } + + interface IPublicKeyObject { + + ExportPublicKey(output) : Result + } + + interface ISymmetricKeyObject { + + GetAllowedModes() : vector + } + + IKeyObject --|> ICryptoObject : inherits + IPrivateKeyObject --|> IKeyObject : inherits + IPublicKeyObject --|> IKeyObject : inherits + ISymmetricKeyObject --|> IKeyObject : inherits +} + +' ============================================================ +' Key Slot Object +' ============================================================ +package "Slot Objects" { + interface IKeySlotObject { + + GetInfo() : KeySlotInfo + + GetState() : KeySlotState + + GetAllowedAlgorithm() : AlgorithmId + + GetPrimaryProvider() : uint16_t + + GetCompatibleProviders() : vector + + GetPermittedOperations() : KeyOperationPermission + } + + interface ICertSlotObject { + + IsOccupied() : bool + } + + IKeySlotObject --|> ICryptoObject : inherits + ICertSlotObject --|> ICryptoObject : inherits +} + +' ============================================================ +' Certificate Objects +' ============================================================ +package "Certificate Objects" { + interface ICertificateObject { + + GetSubject() : string_view + + GetIssuer() : string_view + + GetNotBefore() : int64_t + + GetNotAfter() : int64_t + + GetPublicKeyAlgorithm() : AlgorithmId + } + + ICertificateObject --|> ICryptoObject : inherits +} + +' ============================================================ +' Other Objects +' ============================================================ +package "Other Objects" { + interface IProviderObject + interface IDataObject + interface ISecureObject + + IProviderObject --|> ICryptoObject : inherits + IDataObject --|> ICryptoObject : inherits + ISecureObject --|> ICryptoObject : inherits +} + +' ============================================================ +' Access note +' ============================================================ +note bottom of ICryptoObject + Obtained via ICryptoContext accessor methods: + GetKeyObject(), GetKeySlotObject(), + GetCertificateObject(), GetCertSlotObject(), + GetProviderObject() +end note + +@enduml diff --git a/docs/crypto/architecture/component_overview.png b/docs/crypto/architecture/component_overview.png new file mode 100644 index 0000000000000000000000000000000000000000..355c3ef4ca416be711a9fbf16a33a1df7ffbb5bc GIT binary patch literal 165939 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 literal 0 HcmV?d00001 diff --git a/docs/crypto/architecture/component_overview.puml b/docs/crypto/architecture/component_overview.puml new file mode 100644 index 0000000..98a9623 --- /dev/null +++ b/docs/crypto/architecture/component_overview.puml @@ -0,0 +1,179 @@ +@startuml Component Overview +title __Architecture overview__ + +set namespaceSeparator :: + +' ==================================================================== +' CLIENT SIDE +' ==================================================================== +package "Client Library (score::mw::crypto)" <> { + interface api::ICryptoStack { + +CreateCryptoContext() : ICryptoContext + } + interface api::ICryptoContext { + +ResolveResource(...) : CryptoResourceId + +CreateHashContext(...) : IHashContext + +CreateCipherContext(...) : ICipherContext + +...() + } + api::ICryptoStack ..> api::ICryptoContext : creates +} + +' ==================================================================== +' IPC TRANSPORT +' ==================================================================== +package "IPC (score::crypto::ipc)" <> { + class ipc::GrpcControlServer { + +Start(socket_path) + +Stop() + +WaitForTermination() + } +} + +' ==================================================================== +' DAEMON +' ==================================================================== +package "Daemon (score::crypto::daemon)" <> { + + ' --- Control Plane --- + package "control_plane/" <> #F5F5F5 { + + interface control_plane::IControlServer { + +Start(socket_path) + +Stop() + +WaitForTermination() + } + + interface control_plane::IHandlerChainFactory { + +CreateRequestHandler() : IRequestHandler + } + + interface control_plane::IRequestHandler { + +processRequest(request) : ControlResponse + } + + class control_plane::BasicHandlerChainFactory { + +CreateRequestHandler() : IRequestHandler + } + + class control_plane::ConnectionHandler { + -m_next : IRequestHandler + +processRequest(request) : ControlResponse + } + + ipc::GrpcControlServer .up.|> control_plane::IControlServer + ipc::GrpcControlServer *-- control_plane::IHandlerChainFactory : owns + control_plane::BasicHandlerChainFactory .up.|> control_plane::IHandlerChainFactory + control_plane::ConnectionHandler .up.|> control_plane::IRequestHandler + control_plane::BasicHandlerChainFactory ..> control_plane::ConnectionHandler : creates + } + + ' --- Mediator --- + package "mediator/" <> #F0F0FF { + interface mediator::IMediator { + +processRequest(request) : ControlResponse + } + class mediator::MediatorImpl { + +processRequest(request) : ControlResponse + } + mediator::IMediator .up.|> control_plane::IRequestHandler + mediator::MediatorImpl .up.|> mediator::IMediator + control_plane::ConnectionHandler *-- mediator::IMediator : delegates to + } + + ' --- Data Manager --- + package "data_manager/" <> #FFF8DC { + interface data_manager::IDataManager { + +GetNode(...) : DataNode + +CreateNode(...) : DataNodeId + } + mediator::MediatorImpl o-- data_manager::IDataManager + } + + ' --- Key Management --- + package "key_management/" <> #F0FFF0 { + class key_management::KeyManagementModule { + +{static} Create(...) : Sptr + +GetService() : KeyManagementService::Sptr + } + class key_management::KeyManagementService { + +ResolveKeySlot(...) : Expected + +RegisterKeyMaterial(...) + +BindKeyToContext(...) + } + key_management::KeyManagementModule *-- key_management::KeyManagementService + mediator::MediatorImpl o-- key_management::KeyManagementService + } + + ' --- Provider --- + package "provider/" <> #E8F5E9 { + + interface provider::IProvider { + +Initialize() : Result + +Shutdown() : Result + } + + interface provider::IProviderFactory { + +CreateAndRegister(manager) : bool + } + + class provider::ProviderManager { + +RegisterFactory(factory) + +Initialize() : bool + +RegisterProvider(name, provider, type) : bool + +GetProvider(id) : IProvider::Sptr + } + + ' --- Config visitor --- + class provider::score_provider::ScoreProviderConfig { + +PopulateDefaults() + +Configure(factory : ScoreProviderFactory&) + } + + class provider::score_provider::ScoreProviderFactory { + -m_configs : vector + +SetConfigs(configs) + +CreateAndRegister(manager) : bool + } + + class provider::score_provider::openssl::OpenSSLProviderFactory { + +CreateAndRegister(manager) : bool + } + + class provider::pkcs11::Pkcs11Config { + +PopulateDefaults() + +Configure(factory : Pkcs11ProviderFactory&) + } + + class provider::pkcs11::Pkcs11ProviderFactory { + -m_configs : vector + +SetTokenConfigs(configs) + +CreateAndRegister(manager) : bool + } + + provider::score_provider::ScoreProviderConfig ..> provider::score_provider::ScoreProviderFactory : Configure() → SetConfigs() + provider::score_provider::ScoreProviderFactory .up.|> provider::IProviderFactory + provider::score_provider::openssl::OpenSSLProviderFactory .up.- provider::score_provider::ScoreProviderFactory : delegates + + provider::pkcs11::Pkcs11Config ..> provider::pkcs11::Pkcs11ProviderFactory : Configure() → SetTokenConfigs() + provider::pkcs11::Pkcs11ProviderFactory .up.|> provider::IProviderFactory + + provider::ProviderManager *-- provider::IProviderFactory : 0..n + provider::ProviderManager *-- provider::IProvider : 1..n + } + + mediator::MediatorImpl o-- provider::ProviderManager + key_management::KeyManagementModule o-- provider::ProviderManager +} + +' ==================================================================== +' CROSS-BOUNDARY CONNECTIONS +' ==================================================================== +api::ICryptoStack ..> ipc::GrpcControlServer : IPC (Unix socket / gRPC) + +legend right + **Not shown:** Logging, session node cleanup, + DataNode tree, key registry, slot registry. +endlegend + +@enduml diff --git a/docs/crypto/architecture/configuration_objects.puml b/docs/crypto/architecture/configuration_objects.puml new file mode 100644 index 0000000..738779c --- /dev/null +++ b/docs/crypto/architecture/configuration_objects.puml @@ -0,0 +1,128 @@ +@startuml score_crypto_api_configuration_objects + +title Score Crypto API — Configuration + +skinparam packageStyle rectangle + +' ============================================================ +' Configuration +' ============================================================ +package "Configuration" { + class BaseContextConfig <> { + + algorithm : AlgorithmId + + provider : optional + + provider_type : optional + + provider_properties : ProviderProperties + + operation_timeout : optional + + timeout_enabled : bool + --- + + SetAlgorithm(alg) : BaseContextConfig& + + SetProvider(prov) : BaseContextConfig& + + SetProviderType(type) : BaseContextConfig& + + SetProviderProperty(key, value) : BaseContextConfig& + + SetOperationTimeout(timeout) : BaseContextConfig& + + DisableTimeout() : BaseContextConfig& + + EnableTimeout() : BaseContextConfig& + } + + class HashContextConfig <> { + + SetAlgorithm(alg) : HashContextConfig& + + SetProvider(prov) : HashContextConfig& + + SetProviderType(type) : HashContextConfig& + } + + class MacContextConfig <> { + + key : CryptoResourceId + --- + + SetAlgorithm(alg) : MacContextConfig& + + SetKey(id) : MacContextConfig& + + SetProvider(prov) : MacContextConfig& + + SetProviderType(type) : MacContextConfig& + } + + class KeyManagementContextConfig <> { + + SetAlgorithm(alg) : KeyManagementContextConfig& + + SetProvider(prov) : KeyManagementContextConfig& + + SetProviderType(type) : KeyManagementContextConfig& + } + + class CipherContextConfig <> { + + key : CryptoResourceId + + direction : CipherDirection + --- + + SetAlgorithm(alg) : CipherContextConfig& + + SetKey(id) : CipherContextConfig& + + SetDirection(dir) : CipherContextConfig& + + SetProvider(prov) : CipherContextConfig& + + SetProviderType(type) : CipherContextConfig& + } + + class SignContextConfig <> { + + key : CryptoResourceId + --- + + SetAlgorithm(alg) : SignContextConfig& + + SetKey(id) : SignContextConfig& + + SetProvider(prov) : SignContextConfig& + + SetProviderType(type) : SignContextConfig& + } + + class VerifySignatureContextConfig <> { + + key : CryptoResourceId + --- + + SetAlgorithm(alg) : VerifySignatureContextConfig& + + SetKey(id) : VerifySignatureContextConfig& + + SetProvider(prov) : VerifySignatureContextConfig& + + SetProviderType(type) : VerifySignatureContextConfig& + } + + class AeadContextConfig <> { + + key : CryptoResourceId + + direction : CipherDirection + --- + + SetAlgorithm(alg) : AeadContextConfig& + + SetKey(id) : AeadContextConfig& + + SetDirection(dir) : AeadContextConfig& + + SetProvider(prov) : AeadContextConfig& + + SetProviderType(type) : AeadContextConfig& + } + + class RandomContextConfig <> { + + SetAlgorithm(alg) : RandomContextConfig& + + SetProvider(prov) : RandomContextConfig& + + SetProviderType(type) : RandomContextConfig& + } + + class CertificateContextConfig <> { + + SetAlgorithm(alg) : CertificateContextConfig& + + SetProvider(prov) : CertificateContextConfig& + + SetProviderType(type) : CertificateContextConfig& + } + + class CertificateVerificationContextConfig <> { + + revocation_policy : optional + --- + + SetRevocationPolicy(policy) : CertificateVerificationContextConfig& + + SetProvider(prov) : CertificateVerificationContextConfig& + + SetProviderType(type) : CertificateVerificationContextConfig& + } + + class CsrGenerationContextConfig <> { + + SetAlgorithm(alg) : CsrGenerationContextConfig& + + SetProvider(prov) : CsrGenerationContextConfig& + + SetProviderType(type) : CsrGenerationContextConfig& + } + + HashContextConfig --|> BaseContextConfig : inherits + MacContextConfig --|> BaseContextConfig : inherits + KeyManagementContextConfig --|> BaseContextConfig : inherits + CipherContextConfig --|> BaseContextConfig : inherits + SignContextConfig --|> BaseContextConfig : inherits + VerifySignatureContextConfig --|> BaseContextConfig : inherits + AeadContextConfig --|> BaseContextConfig : inherits + RandomContextConfig --|> BaseContextConfig : inherits + CertificateContextConfig --|> BaseContextConfig : inherits + CertificateVerificationContextConfig --|> BaseContextConfig : inherits + CsrGenerationContextConfig --|> BaseContextConfig : inherits +} + +@enduml diff --git a/docs/crypto/architecture/design_decisions.rst b/docs/crypto/architecture/design_decisions.rst new file mode 100644 index 0000000..8e06770 --- /dev/null +++ b/docs/crypto/architecture/design_decisions.rst @@ -0,0 +1,1014 @@ +.. + # ============================================================================= + # C O P Y R I G H T + # ----------------------------------------------------------------------------- + # Copyright (c) 2026 by ETAS GmbH. All rights reserved. + # + # The reproduction, distribution and utilization of this file as + # well as the communication of its contents to others without express + # authorization is prohibited. Offenders will be held liable for the + # payment of damages. All rights reserved in the event of the grant + # of a patent, utility model or design. + # ============================================================================= + +.. _crypto_design_decisions: + +Design Decisions +================ + +ABI Compatibility for IPC Layer (Deferred Post-Stabilisation) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. dec_rec:: ABI Compatibility for IPC Layer (Deferred Post-Stabilisation) + :id: dec_rec__crypto__no_abi_compatibility_ipc + :status: proposed + :context: doc__crypto_architecture + :decision: ABI compatibility for the IPC layer between the crypto library and daemon is deferred until the API and wire format are stable. Once stable, a versioned wire format (FlatBuffers is the primary candidate) will be introduced to allow independent deployment of library and daemon versions. + + .. :affects: comp__crypto + +ABI compatibility for the IPC layer between the crypto library and daemon is +deferred until the API and wire format are stable. Once stable, a versioned wire +format (FlatBuffers is the primary candidate) will be introduced to allow +independent deployment of library and daemon versions. + +Context +------- + +The crypto module uses an IPC layer to communicate between the client library and +the daemon process. During initial development, maintaining strict ABI compatibility +would impose versioning overhead (protocol negotiation, backward/forward compatibility +handling) before the interfaces are settled. The decision is therefore to defer ABI +compatibility until the stack is stable, at which point the investment is justified. + +Once the API and wire format are frozen, the following will be required: + +* A versioned wire format with forward/backward compatibility guarantees +* Protocol negotiation mechanisms during connection establishment +* Support for a defined compatibility window (e.g., N−1 daemon with N library) +* Regression testing across supported version combinations + +Decision +-------- + +ABI compatibility for the IPC layer is intentionally deferred for the initial +pre-stable phase. Per machine, only one valid library-daemon combination is +supported until the API stabilises. After stabilisation, a versioned wire format +will be introduced — this decision will be revisited and finalised at that point. + +Consequences +------------ + +**Positive:** + +* Simplified implementation during pre-stable phase — no protocol versioning overhead +* Reduced testing surface — only matching version pairs need validation +* Faster initial development cycle — breaking changes can be made freely +* Clearer deployment model for early adopters — single version per machine + +**Negative:** + +* Library and daemon must currently be updated together as a single unit +* Cannot have multiple applications using different library versions on the same machine +* No graceful degradation when versions mismatch during the deferred phase + +Alternatives Considered +----------------------- + +FlatBuffers +^^^^^^^^^^^ + +FlatBuffers is the primary candidate for the versioned wire format once +stabilisation is reached. + +Advantages +"""""""""" + +* **Schema evolution** — fields can be added with default values without breaking + existing serialised data; removed fields leave a gap that is safely skipped. +* **Zero-copy access** — FlatBuffers tables are accessed in-place from the + shared-memory buffer, aligning with the ``IMemoryAllocator`` zero-copy design. +* **Deterministic layout** — table format is fully specified; no hidden heap + allocation during access. +* **Compact code generation** — generated C++ headers are ``noexcept``-friendly + and free of ``std::function`` or ``std::string`` members. + +Disadvantages +""""""""""""" + +* Final choice is deferred; other candidates (Protocol Buffers, Cap'n Proto, + hand-rolled length-prefixed structs) are not excluded. + +Justification for the Decision +------------------------------ + +The decision to defer ABI compatibility is justified by the current pre-stable phase +of development. Introducing versioning infrastructure before the interfaces are settled +would add maintenance burden without benefit. The FlatBuffers candidate and this +record preserve the intent and analysis so that the versioning work can proceed +efficiently once the stack stabilises. + +--- + +CryptoResourceGuard Lifetime via Daemon-Side Reference Counting +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. dec_rec:: CryptoResourceGuard Lifetime via Daemon-Side Reference Counting + :id: dec_rec__crypto__cry_res_grd_lifetime + :status: accepted + :context: doc__crypto_architecture + :decision: Transient key lifetime is managed exclusively in the daemon via reference counting. The guard holds a type-erased IPC release handle; Create*Context() increments the daemon ref-count atomically. The guard may be destroyed after Create*Context() returns. On client disconnect, the daemon bulk-frees all resources for that client. + + .. :affects: comp__crypto + +Transient key lifetime is managed exclusively in the daemon. The daemon ref-counts +every ephemeral key; the client communicates changes via two IPC calls: +``Release(id)`` (guard destructor) and ``Create*Context(config with key_id)`` +(context creation). + +Context +------- + +Transient crypto resources (keys, certificates) produced within an +``IKeyManagementContext`` or ``ICertificateManagementContext`` session are represented by a +``CryptoResourceGuard``. Key lifetime must survive all contexts actively using the +key and be freed deterministically when neither a guard nor a context holds a +reference. + +Decision +-------- + +Key lifetime is managed in the daemon via a per-key reference count: + +.. list-table:: + :header-rows: 1 + :widths: 55 45 + + * - Event + - Daemon action + * - ``GenerateKey`` / ``DeriveKey`` / ``LoadKey`` / etc. + - Creates key; ref = 1 + * - ``Create*Context(config with key_id)`` + - Validates key alive; ref++ + * - Guard destroyed (``Release(id)`` IPC) + - ref--; free key if ref == 0 + * - Context destroyed + - ref-- for each bound key + * - Client disconnect (crash or normal exit) + - Daemon bulk-frees all resources for that client + +This means: + +- The guard carries only the ``CryptoResourceId`` + and a type-erased IPC release handle (``shared_ptr`` internally). +- ``Create*Context()`` sends a single IPC call that validates the key and atomically + records context ownership. If the guard was released before this call, the daemon + returns ``kResourceNotFound`` — fail-fast, diagnosable behaviour. +- **Crash safety**: on hard process termination (SIGKILL, power loss), no + destructors run. The daemon detects the client disconnect and bulk-frees all + resources. Daemon-side ref-counting is crash-safe by design. +- ``BaseContextConfig`` carries only a ``CryptoResourceId`` for the key. + +**Application contract**: The guard must remain alive (``IsActive()`` returns true) +at the moment ``Create*Context()`` is called. After that call returns successfully, +the guard may be destroyed in any order relative to the context. + +.. code-block:: cpp + + auto key = key_mgmt->GenerateKey("AES-256").value(); + CipherContextConfig config; + config.SetAlgorithm("AES-256-GCM").SetKey(key).SetDirection(CipherDirection::kEncrypt); + // guard must be alive here: + auto cipher = ctx->CreateCipherContext(config).value(); + // Daemon has incremented the key ref-count. Guard may now be destroyed. + // ... use cipher ... + +Explicit Release and Guard Synchronisation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The daemon is the sole source of truth for resource validity. +The guard's ``active_`` flag is a client-side hint, not a daemon query. + +**Normal path — destructor** (no application code needed): + +The guard destructor sends ``Release(id)`` IPC when active, silently +swallowing the result (destructors cannot propagate errors). + +**Explicit path — synchronous error handling**: + +When the application needs to explicitly confirm release before destruction, call +``guard.Release()``: + +.. code-block:: cpp + + auto result = guard.Release(); // explicit, returns Result + if (result.has_value()) { + // no-op destructor + } + +**Persist path — copy semantics**: + +``IKeyManagementContext::PersistKey(const CryptoResourceId&, slot)`` takes the +ephemeral key by ID (copy semantics). The guard that produced the ID remains +active after ``PersistKey`` returns. The ephemeral copy continues to exist until +the guard is released or goes out of scope; the persisted slot copy is +independent: + +.. code-block:: cpp + + auto key = key_mgmt->GenerateKey((GenerateKeyParams{}.SetAlgorithm("AES-256"))).value(); + key_mgmt->PersistKey(slot, key).value(); // key (guard) remains active + // 'key' still holds the ephemeral copy — explicit Release() or destructor frees it + // 'slot' is the persistent copy — use slot handle for all future durable operations + + +``SetKey`` and Implicit Guard Conversion +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``CryptoResourceGuard`` provides an implicit conversion operator to +``const CryptoResourceId&``. All key-accepting config structs expose a single +``SetKey(const CryptoResourceId& k)`` overload. Passing a guard works directly +via this conversion — no extra overload is needed: + +.. code-block:: cpp + + auto key = key_mgmt->GenerateKey((GenerateKeyParams{}.SetAlgorithm("AES-256"))).value(); + // guard must be alive when CreateCipherContext is called: + auto cipher = ctx->CreateCipherContext( + CipherContextConfig{}.SetAlgorithm("AES-256-GCM").SetKey(key) + .SetDirection(CipherDirection::kEncrypt)).value(); + // Daemon incremented key ref-count. Guard may now be independently destroyed. + + cipher->Init(iv); + cipher->Finalize(out_span); + +Consequences +------------ + +**Positive:** + +* Single source of truth for key lifetime: the daemon. No client-side + ``shared_ptr`` propagation across API boundaries. +* Crash-safe: daemon bulk-frees on disconnect regardless of client destructor state. +* Fail-fast: releasing a guard before ``Create*Context`` is called returns + ``kResourceNotFound`` — diagnosable, deterministic behaviour. +* ``BaseContextConfig`` carries only ``CryptoResourceId`` — minimal and flat. + +**Negative:** + +* Guards must remain alive until ``Create*Context()`` returns — an intuitive + but explicit application contract. +* Daemon must maintain per-key ref-counts for all active ephemeral keys. + Memory cost is bounded by max concurrent keys (deployment-time constant). + +--- + +Shared-Connection Anchor for Persistent Resource ID Stability +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. dec_rec:: Shared-Connection Anchor for Persistent Resource ID Stability + :id: dec_rec__crypto__conn_anchor_persistent_ids + :status: accepted + :context: doc__crypto_architecture + :decision: The IPC connection to the crypto daemon is the anchor for kPersistent resource IDs. All ICryptoStack instances within the same application share a single connection, ensuring that the same resource name always resolves to the same numeric CryptoResourceId regardless of which stack or context resolves it. ID assignment is on-demand per connection, and each application gets its own isolated connection with an independent ID namespace. + + .. :affects: comp__crypto + +The application-level daemon connection is a shared anchor across all +``ICryptoStack`` instances within the same application. Persistent resource IDs +(``kPersistent``) are assigned on-demand per connection and remain stable for the +connection's lifetime, independent of any individual ``ICryptoContext``. + +Context +------- + +Persistent resources (key slots, stored certificates, CRLs) outlive any +individual context or session. Tying the validity of their ``CryptoResourceId`` +handle to the lifetime of a specific ``ICryptoContext`` would couple context +lifetime management to resource identifier validity unnecessarily. The desired +property is that a resolved persistent ID remains usable as long as the +application is connected to the daemon — independent of which context or stack +resolved it. + +Decision +-------- + +The **connection** (the underlying transport connection to the crypto daemon) +is the anchor for ``kPersistent`` resource IDs: + +* All ``ICryptoStack`` instances within the same application share a single + connection. Multiple stacks always resolve the same resource name to the + same numeric ``CryptoResourceId``. +* ID assignment is **on-demand**: a numeric ``id`` is assigned when a resource + is first resolved by any stack on the connection. Subsequent resolutions of + the same name by any stack return the identical ``id``. +* Each application gets its own isolated connection with an independent ID + namespace — IDs are not globally stable across application restarts or + across different applications. This preserves the security property: + IDs are per-application-unique and not externally predictable. +* Connection ID tracking is managed by the underlying IPC transport layer + as an implementation detail, not exposed to library users. + +Lifetime Strategy (Deployment Choice) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The connection lifetime is a deployment-level choice between two strategies: + +1. **Reference-counted (early cleanup):** Connection is destroyed when the last + ``ICryptoStack`` referencing it is destroyed. Daemon-side resources are + freed immediately. Suitable when stacks are short-lived and memory + footprint must be minimised. +2. **Fixed (application lifetime):** Connection is created once at application + startup and destroyed when the application terminates. Daemon resources + are held in daemon memory for the application lifetime. Simpler to reason + about; preferred when stacks are frequently created and destroyed. + +Both strategies are implementation details of the transport layer — the +public ``ICryptoStack`` and ``ICryptoContext`` API is identical in either case. +Cross-application connection sharing is **not allowed**; +``IConnectionAnchor`` remains a private implementation type. + +Scope +^^^^^ + +Applies to ``kPersistent`` resources only: ``kKeySlot``, ``kCertificate``, +``kCertSlot``, ``kVerificationTrustStore``. Ephemeral (``kKey``) IDs remain session-scoped +(valid only within the ``IKeyManagementContext`` session that produced them). + +IPC Schema +^^^^^^^^^^ + +Whether per-connection ID assignment and the registry require new proto +messages or can reuse the existing ``CryptoResourceId`` wire format is an +open implementation item for the daemon development to resolve before the +daemon is built. + +Consequences +------------ + +**Positive:** + +* Independent context lifetime for persistent resources — ``ICryptoContext`` + can be destroyed after resolution without invalidating the ``CryptoResourceId``. +* Stable, deterministic IDs within an application — same name always resolves + to the same ``id`` regardless of which stack or context resolves it. +* No security leakage — IDs are per-application-unique, on-demand assigned, + not globally stable or predictable from outside the application. +* Applications can share resolved ``CryptoResourceId`` values between stacks + without re-resolving. + +**Negative:** + +* Daemon must maintain a per-connection ID registry (map from resource name + to numeric ``id``). Memory cost is bounded by the number of distinct + persistent resources accessed per application. +* With fixed-lifetime strategy: daemon memory for resolved IDs is held for + the entire application lifetime even if the IDs are no longer used. + +--- + +``AlgorithmId`` as ``FixedCapacityString<64>`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. dec_rec:: AlgorithmId Represented as FixedCapacityString<64> + :id: dec_rec__crypto__alg_id_fixed_cap_str + :status: accepted + :context: doc__crypto_architecture + :decision: ``AlgorithmId`` is defined as ``FixedCapacityString<64>`` rather than a compile-time enum or std::string, providing open-set extensibility with deterministic stack allocation. 64 bytes covers the longest currently-known PQC identifier (e.g., "SLH-DSA-SHA2-128s") with comfortable headroom. All constructors and assignments are noexcept. + + .. :affects: comp__crypto + +``AlgorithmId`` is defined as ``FixedCapacityString<64>`` rather than a +compile-time enum or ``std::string``, providing open-set extensibility with +deterministic stack allocation. + +Context +------- + +Algorithm identifiers must accommodate current algorithms (e.g., ``"AES-256-GCM"``, +``"SHA-256"``, ``"SLH-DSA-SHA2-128s"``), future PQC schemes, and provider-specific +extensions — an open set that cannot be enumerated at compile time. + +Decision +-------- + +Three candidate representations were evaluated: + +1. **``enum class AlgorithmId``** — type-safe, zero overhead, but a *closed* set. + Adding a new PQC algorithm requires recompiling the library and all callers. + Incompatible with the extensibility goal and with runtime-configured providers. + +2. **``std::string``** — open set, but heap-allocating. Every ``AlgorithmId`` value + creates at least one heap allocation. Violates MISRA A18-5-1, incompatible with + ASIL ``noexcept`` destructors, and introduces non-deterministic WCET. + +3. **``FixedCapacityString<64>``** — open set, stack-allocated, exception-free + (oversized input silently truncated, ``truncated()`` flag set). 64 bytes covers + the longest currently-known PQC identifier (``"SLH-DSA-SHA2-128s"`` = 18 chars) + with comfortable headroom. All constructors and assignments are ``noexcept``. + +``FixedCapacityString<64>`` (option 3) was selected. The same rationale applies to +``ResourceId`` (``FixedCapacityString<64>``) and ``ProviderInfo::name`` +(``FixedCapacityString<32>``). + +Consequences +------------ + +**Positive:** + +* Zero heap allocation for any algorithm or resource identifier. +* Open set — new PQC algorithms deploy at daemon level; no library recompile needed. +* ``noexcept`` constructors and assignments — compatible with ASIL containers. +* ``GetAlgorithm()``, ``GetAllowedAlgorithm()``, ``GetPublicKeyAlgorithm()`` are + now ``const noexcept``, enabling use in safety-annotated code. + +**Negative:** + +* 64-byte fixed storage regardless of actual string length (minor waste for short names). +* Silent truncation on overflow — callers must check ``truncated()`` when constructing + from untrusted input. +* No compile-time algorithm validation — typos become runtime errors. + +--- + +Synchronous-Only IPC Model +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. dec_rec:: Synchronous-Only IPC Model + :id: dec_rec__crypto__synchronous_ipc + :status: accepted + :context: doc__crypto_architecture + :decision: All IPC calls between the client library and the crypto daemon are synchronous (blocking). No callback, future, or async-notify pattern is exposed in the V1 public API. + + .. :affects: comp__crypto + +All IPC calls between the client library and the crypto daemon are synchronous +(blocking). No callback, future, or async-notify pattern is exposed in the V1 +public API. + +Context +------- + +A synchronous model blocks the calling thread for the full IPC round-trip plus +provider execution. The alternatives introduce heap usage, threading complexity, +or event-loop coupling that are incompatible with MISRA and automotive middleware +requirements. + +Decision +-------- + +All IPC calls are synchronous (blocking). Asynchronous alternatives were evaluated +and rejected: + +* **Future/Promise API** — ``Result>`` return types, daemon uses a + worker-thread pool. This would result in higher code complexity. +* **Callback model** — caller provides a ``std::function)>`` invoked + on completion. ``std::function`` violates MISRA A18-5-1 (heap). Custom fixed-size + delegate types possible but add significant complexity. +* **Polling / event-loop** — caller polls a completion token. Couples the crypto API + to the application event loop; not idiomatic for automotive middleware. + +Consequences +------------ + +**Positive:** + +* Simple, predictable call semantics — no threading model imposed on callers. +* Full ``Result`` error propagation on every call. +* MISRA-compliant — no ``std::function``, no heap, no thread creation in library. +* Operation timeouts (``dec_rec__crypto__two_layer_timeout``) provide bounded + blocking behaviour — the calling thread never blocks indefinitely. + +**Negative:** + +* Thread blocks for full operation duration including IPC + provider execution. +* Potential priority inversion in RTOS environments with high-priority callers. +* Cannot pipeline or batch multiple crypto operations. +* Future async API (``GenerateKeyAsync``) must be added as an extension; + The daemon-side ref-count model (see ``dec_rec__crypto__cry_res_grd_lifetime``) + naturally accommodates async key generation: the daemon can atomically bind the + key to the context when the async result is consumed. + +--- + +Context ``Reset()`` for Streaming Context Reuse +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. dec_rec:: Context Reset() for Streaming Context Reuse + :id: dec_rec__crypto__context_reset_reuse + :status: accepted + :context: doc__crypto_architecture + :decision: ``IStreamingContext`` exposes a ``Reset()`` method returning the context to + + .. :affects: comp__crypto + +``IStreamingContext`` exposes a ``Reset()`` method returning the context to +post-construction state after ``Finalize()``, preserving key, algorithm, and config +bindings. This avoids repeated factory + IPC overhead for same-configuration +repeated operations. + +Context +------- + +Streaming contexts (hash, sign, verify, encrypt, decrypt, MAC, AEAD) follow the +``Init()`` → ``Update()`` * → ``Finalize()`` state machine. Without reuse, each +new operation requires: (1) ``ICryptoContext::Create*Context()`` IPC call, +(2) ``Init()`` IPC call. For high-frequency operations (e.g., per-frame hashing, +per-message MAC), this doubles the IPC overhead. + +Decision +-------- + +``Reset()`` is added to ``IStreamingContext``. Alternatives evaluated: + +* **Destroy and re-create** — simplest, but doubles IPC cost per operation. +* **``Reset()`` on ``IStreamingContext``** — single IPC call to the daemon to clear + provider-side state; key, algorithm, config, and session handle remain valid. + State machine transitions: ``kFinalized`` → ``kCreated`` on success. + ``kContextResetFailed`` (``0x01070003``) reported on daemon-side failure. +* **Context pooling in the library** — a pool of pre-created contexts returned to + callers. Adds ~200 LOC of pool management, thread-safety concerns, and a fixed + pool bound that may be too large or too small for different deployments. + +Consequences +------------ + +**Positive:** + +* Halves IPC round-trips for high-frequency same-configuration operations. +* No API surface change — ``Reset()`` is additive; callers that destroy and + re-create continue to work unchanged. +* Key, algorithm, and config bindings preserved — no re-configuration needed. +* Single additional error code (``kContextResetFailed``) for daemon failure path. + +**Negative:** + +* Daemon must track context state and clear provider-side buffers on ``Reset()``. +* Callers must ensure ``Finalize()`` is called before ``Reset()``; calling + ``Reset()`` in ``kUpdating`` state returns ``kInvalidOperation``. + +--- + +Two-Layer Per-Call Timeout with Daemon-Side Enforcement +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. dec_rec:: Two-Layer Per-Call Timeout with Daemon-Side Enforcement + :id: dec_rec__crypto__two_layer_timeout + :status: accepted + :context: doc__crypto_architecture + :decision: Two-layer timeout model selected for bounded behaviour at both layers. + + .. :affects: comp__crypto + +Operation timeouts are enforced at two layers — a stack-wide default in +``CryptoStackConfig`` and a per-context override in ``BaseContextConfig`` with an +explicit ``DisableTimeout()`` escape — with the daemon as the enforcement point. +Client threads unblock on timeout; timed-out contexts transition to ``kError``. + +Context +------- + +Without timeouts, a hung HSM or stalled IPC channel blocks the calling thread +indefinitely — a safety violation for ISO 26262 ASIL functions. A single global +timeout is insufficient because some legitimate operations (e.g., RSA-4096 key +generation on software providers) require more time than typical operations. +Client-side-only timeouts fail to release daemon-held resources when the deadline +expires. + +Decision +-------- + +Three timeout models were evaluated: + +1. **Client-side only (``std::future::wait_for``)** — client unblocks after + deadline, but the daemon continues executing the stalled operation, holding + the resource. Does not provide bounded daemon resource usage. + +2. **Daemon-side only (watchdog thread per context)** — daemon kills stalled + operations after timeout and notifies client via error response. Provides + bounded resource usage but requires the daemon to maintain one watchdog + timer per in-flight context. + +3. **Two-layer: client deadline + daemon enforcement** — client passes + timeout value on each IPC call; daemon enforces the deadline server-side. + If deadline expires, daemon transitions context to error, releases provider + resources, and returns ``kOperationTimedOut`` to client. ``DisableTimeout()`` + is available for legitimately long operations (e.g., RSA-4096 key generation + on software providers). + +Model 3 was selected for bounded behaviour at both layers. + +Consequences +------------ + +**Positive:** + +* Guaranteed bounded execution — no call blocks indefinitely. +* Daemon-side enforcement means provider resources are released on timeout, + not just the client thread. +* Per-context override allows fine-grained tuning without changing global config. +* ``DisableTimeout()`` supports legitimate long-running operations without + disabling safety globally. +* Satisfies ISO 26262 Part 6 Table 1 (bounded execution time for safety functions). + +**Negative:** + +* Daemon must maintain a per-context deadline and cancel infrastructure. +* ``DisableTimeout()`` is a safety escape hatch; misuse (e.g., in Safety paths) + must be justified in the application's safety case. +* Two-layer design adds complexity compared to a single global timeout. + +--- + +``KeyOperationPermission`` as a Capability Bitmask +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. dec_rec:: KeyOperationPermission as a Capability Bitmask + :id: dec_rec__crypto__key_op_permission_bitmask + :status: accepted + :context: doc__crypto_architecture + :decision: Per-key usage restrictions are encoded as a ``KeyOperationPermission`` bitmask (named bit-flag capabilities: ``kEncrypt``, ``kDecrypt``, ``kSign``, ``kVerify``, ``kDerive``, ``kWrap``, ``kExport``, ``kGenerate``). The daemon enforces the bitmask at context creation time; operations not permitted by the key's bitmask return ``kKeyOperationNotPermitted``. + + .. :affects: comp__crypto + +Per-key usage restrictions are encoded as a ``KeyOperationPermission`` bitmask +(named bit-flag capabilities: ``kEncrypt``, ``kDecrypt``, ``kSign``, ``kVerify``, +``kDerive``, ``kWrap``, ``kExport``, ``kGenerate``). The daemon enforces the bitmask +at context creation time; operations not permitted by the key's bitmask return +``kKeyOperationNotPermitted``. + +Context +------- + +A key provisioned for signing must not be usable for encryption or export. +Enforcing usage restrictions at the API level prevents misuse even if the caller +has a valid resource handle. The representation must be heap-free and extensible +to support future operations without breaking existing code. + +Decision +-------- + +Three designs were evaluated: + +1. **``std::unordered_set``** — flexible but heap-allocating. + Violates MISRA A18-5-1, incompatible with ASIL ``noexcept`` requirements. + +2. **``KeyPermissions`` struct with boolean flags** — type-safe, stack-allocated, + but verbose (one field per operation). Extending to a new operation requires + a breaking ABI change. + +3. **Bitmask (``uint32_t`` or ``enum class`` with ``operator|``)** — compact, + heap-free, trivially copyable, extensible (new bits added without breaking + existing code). Standard pattern in OS capability models (POSIX, seL4). + +The bitmask (option 3) was selected for compactness and extensibility. + +Consequences +------------ + +**Positive:** + +* Compact representation — one ``uint32_t`` field in key metadata. +* Extensible — new operations add a new named bit; existing bitmasks remain valid. +* Daemon enforces at context creation — enforcement point is in the trusted boundary. +* Two-layer access control: *who* (``ResolveResource()`` ACL, uid) and + *what* (``KeyOperationPermission`` bitmask per key). + +**Negative:** + +* Bitmask operations (``&``, ``|``, ``~``) are less type-safe than a method API; + callers can accidentally construct invalid combinations. +* No runtime check that a bitmask value is a valid combination of named bits + (mitigated by providing named constants and ``operator|`` overloads). + +--- + +Unified Cipher Context (No Encrypt/Decrypt or Symmetric/Asymmetric Split) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. dec_rec:: Unified Cipher Context Rather Than Separate Encrypt/Decrypt Plus Symmetric/Asymmetric Split + :id: dec_rec__crypto__unified_enc_dec_contexts + :status: accepted + :context: doc__crypto_architecture + :decision: A single ``ICipherContext`` type is used for both encryption and decryption, with direction configured via ``CipherContextConfig``. Sign, verify, MAC, and AEAD are each separate context types under the ``IStreamingContext`` hierarchy. The algorithm identifier and key type determine whether the operation is symmetric or asymmetric at runtime. Separate ``SymmetricEncryptContext`` / ``AsymmetricEncryptContext`` types were rejected for increased complexity without benefit. + + .. :affects: comp__crypto + +Encrypt and decrypt share a single ``ICipherContext`` (direction set via +``CipherContextConfig``). Sign, verify, MAC, and AEAD are each a separate context +type under the ``IStreamingContext`` hierarchy. The algorithm identifier and key +type determine whether the operation is symmetric or asymmetric at runtime. +Separate ``SymmetricEncryptContext`` / ``AsymmetricEncryptContext`` types were +rejected. + +Context +------- + +A split design creates a separate class per algorithm family. Adding PQC KEM +support requires a new class even if the streaming interface is identical. This +increases include weight and virtual dispatch hierarchy depth without benefit, +and makes the API surface grow with the algorithm set. + +Decision +-------- + +Three designs were evaluated: + +1. **Symmetric/Asymmetric split** — mirrors algorithm families at the type level. + Callers must select the correct context type; runtime algorithm selection within + a family is still possible. Adds N context types per new algorithm family. + Increases include weight and virtual dispatch hierarchy depth. + +2. **Unified context per operation** — ``ICipherContext`` (encrypt + decrypt unified), + ``ISignContext``, ``IVerifySignatureContext``, ``IMacContext``, ``IAeadContext``. + Algorithm and key type determine behaviour. Adding ML-KEM or ML-DSA requires + no new context type — only a new algorithm name string and provider support. + +3. **Single universal context** — one ``ICryptoOperationContext`` with a mode + parameter. Rejects the SRP; makes misuse easier (e.g., calling ``Sign()`` on a + context configured for encryption). Rejected for API clarity reasons. + +Design 2 was selected for the balance of clarity and extensibility. + +Consequences +------------ + +**Positive:** + +* Flat, stable hierarchy — five concrete context types regardless of algorithm count. +* PQC extensibility — ML-KEM, ML-DSA, SLH-DSA require no new context types. +* Each context enforces its own state machine; misuse (e.g., ``Update()`` before + ``Init()``) returns a typed error regardless of algorithm. +* Consistent ``Init()`` / ``Update()`` / ``Finalize()`` / ``Reset()`` pattern + across all operations simplifies caller code. + +**Negative:** + +* Algorithm-specific overloads (e.g., ``ICipherContext::Init(iv)`` vs. + ``IStreamingContext::Init()``) require ``using`` declarations to suppress + name-hiding warnings (see §3.5 in the evaluation report). +* AEAD tag handling (``SetTag()`` / ``GetTag()``) cannot be expressed identically + to non-AEAD operations — ``IAeadContext`` requires additional methods. + +--- + +``IMemoryAllocator`` Separated from ``ICryptoStack`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. dec_rec:: IMemoryAllocator Separated from ICryptoStack + :id: dec_rec__crypto__memory_allocator_separation + :status: accepted + :context: doc__crypto_architecture + :decision: ``IMemoryAllocator`` is a standalone interface independent of ``ICryptoStack``. It represents the data plane, can be injected into components that need only memory management, and enables isolated unit testing — none of which are possible if it is coupled to the control-plane ``ICryptoStack``. + + .. :affects: comp__crypto + +``IMemoryAllocator`` is a standalone interface independent of ``ICryptoStack``. +It represents the data plane, can be injected into components that need only +memory management, and enables isolated unit testing — none of which are possible +if it is coupled to the control-plane ``ICryptoStack``. + +Context +------- + +The crypto module uses shared memory as the zero-copy data plane between the +application and the crypto daemon. A naive design would expose memory allocation +directly through ``ICryptoStack``, coupling the data plane to the control-plane +IPC object. Three reasons motivated a separate interface: + +1. **Architectural independence of data plane and control plane** — the memory + subsystem operates independently of IPC: buffers can be allocated, written, + and passed to providers without any IPC call. Coupling allocation to + ``ICryptoStack`` would obscure this separation. + +2. **Independent injection** — components that need only memory management + (e.g., a buffer pool, a serialiser) can receive an ``IMemoryAllocator`` + without depending on the full ``ICryptoStack`` interface. This is consistent + with the Interface Segregation Principle and reduces unnecessary coupling + in the component graph. + +3. **Isolated unit testing** — by taking ``IMemoryAllocator`` as a dependency, + individual components can be tested with a mock or stub allocator without + standing up an ``ICryptoStack`` or a daemon connection. + +Decision +-------- + +``IMemoryAllocator`` is defined as a standalone interface independent of +``ICryptoStack``. Applications obtain both objects separately; the memory allocator +is the data plane and the crypto stack is the control plane. Cross-application +connection sharing is not supported; each application has its own allocator instance. + +Consequences +------------ + +**Positive:** + +* Data-plane and control-plane concerns are visibly separated in the API. +* Components that allocate buffers do not depend on ``ICryptoStack``. +* Unit tests for memory-dependent components are cheaper and hermetic. +* The zero-copy path (``kProviderCompatible`` allocation) is a data-plane concern + and sits cleanly on ``IMemoryAllocator`` without polluting ``ICryptoStack``. + +**Negative:** + +* Applications must obtain and manage two objects (``ICryptoStack`` + ``IMemoryAllocator``) + where a monolithic interface would require only one. + +--- + +Control Plane IPC Boundary Copy with Daemon-Internal Zero-Copy References +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. dec_rec:: Control Plane IPC Boundary Copy with Daemon-Internal Zero-Copy References + :id: dec_rec__crypto__ipc_boundary_copy + :status: accepted + :context: doc__crypto_architecture + :decision: The control plane IPC layer on the daemon side shall create exactly one owning copy of all incoming request data at the deserialization boundary. All subsequent daemon-internal processing shall operate on non-owning references into that single owned copy. This prevents time-of-check-time-of-use (TOCTOU) attacks on control information while minimising memory copies within the daemon. The data plane is explicitly excluded — it carries only opaque data payloads and may use zero-copy transfer. + +The control plane IPC layer on the daemon side shall create exactly one owning +copy of all incoming request data at the deserialization boundary. All subsequent +daemon-internal processing shall operate on non-owning references into that single +owned copy. This prevents time-of-check-time-of-use (TOCTOU) attacks on control +information while minimising memory copies within the daemon. The data plane is +explicitly excluded — it carries only opaque data payloads and may use zero-copy transfer. + +Context +------- + +This decision applies to the **control plane** only — the channel carrying +operation requests, responses, and their parameters (key identifiers, algorithm +names, operation codes, session IDs, in-band data). + +The **data plane** is **out of scope** and may use zero-copy (e.g., shared +memory). The data plane carries only opaque payloads (plaintext, ciphertext) — +not control information that the daemon validates or routes upon — so TOCTOU +modification cannot cause the daemon to mis-route an operation, use the wrong +key, or bypass access control. At worst the provider processes corrupted input, +equivalent to the client submitting bad data in the first place. + +Control plane requests carry parameters from an untrusted client. The daemon +must decide whether to work directly from the transport buffer or copy first. + +Consequences +------------ + +**Positive:** + +* Eliminates TOCTOU on control information. +* One copy at ingress, zero through the handler chain, one at egress. +* Clear ownership — the request structure owns; all handlers borrow. +* Data plane stays zero-copy for bulk payloads. + +**Negative:** + +* Mandatory copy per control plane request, even when the transport buffer is + safe (deliberate performance-for-security trade-off; overhead is small since + the control plane carries only metadata and small in-band buffers). +* Two representations needed in the protocol types: owning types at the IPC + boundary, non-owning views internally. + +Alternatives Considered +----------------------- + +1. **Zero-copy end-to-end** — read directly from the transport buffer. Vulnerable + to TOCTOU: a client could swap a key ID or algorithm name between validation + and use. + +2. **Copy at every layer boundary** — excessive allocation; e.g., three copies of + the same hash input across IPC adapter, mediator, and provider. + +3. **Single copy at the control plane IPC boundary, references thereafter** — the + deserialization layer copies all mutable parameters into daemon-owned memory. + Downstream layers receive const references and use non-owning views. + +Strategy 3 was selected. + +Justification for the Decision +------------------------------ + +Control information determines *which* operation runs, *with which key*, *using +which algorithm*. If the daemon reads this from a buffer still writable by the +client, the client can mutate it between validation and use (TOCTOU). A single +owning copy at the IPC boundary makes the request immutable from the client's +perspective. + +Non-owning references within the daemon are safe because: + +* The owned request data outlives the entire synchronous processing chain. +* All downstream handlers receive it as const. +* Processing is single-threaded per request. + +The data plane does not carry control information, so TOCTOU cannot redirect +operations — zero-copy is safe there by design. + +--- + +Per-Operation Parameter Structs with Dual Overloads +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. dec_rec:: Per-Operation Parameter Structs with Dual Overloads + :id: dec_rec__crypto__per_op_params + :status: proposed + :context: doc__crypto_architecture + :decision: Key management operations (``GenerateKey``, ``DeriveKey``, ``AgreeKey``, ``UnwrapKey``, ``ImportKey``, ``WrapKey``) are encapsulated in dedicated fluent-builder parameter structs. Dual overloads support both ephemeral keys (returning ``CryptoResourceGuard``) and direct-to-slot writes (returning ``bool``). KDF configuration is represented as a structured ``KdfParameters`` type rather than opaque byte spans, providing type safety and extensibility. + + .. :affects: comp__crypto + +Each key management operation accepts parameters via a dedicated fluent-builder +struct (``GenerateKeyParams``, ``DeriveKeyParams``, ``AgreeKeyParams``, +``WrapKeyParams``, ``UnwrapKeyParams``, ``ImportKeyParams``). Dual overloads +support both ephemeral and persistent slot targets. KDF configuration is +expressed as a structured ``KdfParameters`` type containing typed fields for +all supported KDFs. + +Context +------- + +Key operations have complex parameter needs: algorithm selection, permission +bitmasks, exportability flags, KDF configuration, IV, AAD, wrapping algorithm, +peer public key data, and format specifiers. These parameters must be passed +safely and extensibly to support both ephemeral keys (returned to the caller) +and direct-to-slot writes (persisted in the daemon). + +Decision +-------- + +Six dedicated parameter structs are provided: + +* ``GenerateKeyParams`` — algorithm, permissions, slot size +* ``DeriveKeyParams`` — algorithm, permissions, KDF config, salt, label +* ``AgreeKeyParams`` — peer public key, algorithm, permissions +* ``WrapKeyParams`` — wrapping algorithm, IV, AAD +* ``UnwrapKeyParams`` — format specifier, permissions +* ``ImportKeyParams`` — algorithm, permissions, format specifier + +Each struct is a fluent builder with named setters (``SetAlgorithm()``, +``SetPermissions()``, etc.), enabling readable call sites. + +Dual overloads are provided for all key-producing operations: + +* **Ephemeral overload**: ``Result XxxKey(const XxxxKeyParams&)`` +* **Persistent overload**: ``Result XxxKey(const CryptoResourceId& target_slot, const XxxxKeyParams&)`` + +The ``target_slot`` parameter is always first in slot-targeting overloads, +consistent with ``PersistKey(target_slot, ephemeral_key)``. + +KDF configuration is replaced with a structured ``KdfParameters`` struct +containing typed fields (salt, label, iteration count, output length) for +all supported KDFs: HKDF, TLS 1.2 PRF, TLS 1.3 HKDF, PBKDF2, SP800-108. +Opaque byte spans are no longer used for KDF parameters. + +Alternatives Considered +----------------------- + +Single Fat Config Struct +^^^^^^^^^^^^^^^^^^^^^^^^ + +One ``KeyOperationConfig`` for all operations, with optional fields for each +mode. This is rejected: most fields are unused for any given operation, creating +confusion and enabling invalid parameter combinations at compile time. A per-operation +struct enforces that only valid parameters are set. + +Separate Named Methods +^^^^^^^^^^^^^^^^^^^^^^ + +Methods like ``GenerateKeyToSlot()`` instead of overloads. This is rejected: +it doubles the API surface without adding clarity. Overloads are distinguished +by return type (``CryptoResourceGuard`` vs ``bool``) and by the presence of +``target_slot`` as the first parameter, providing clear intent. + +Builder Pattern on Context +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A fluent chain on the context object (e.g., ``key_mgmt->Generate().Algorithm("AES-256").Execute()``). +This is rejected: it requires runtime validation of missing required fields. +A params-struct approach catches missing required fields at compile time via +member initialization (the daemon performs final validation). + +Consequences +------------ + +**Positive:** + +* Named fields and fluent builders eliminate parameter-order confusion and + enable readable call sites with self-documenting intent. +* Adding new optional fields to a params struct is non-breaking; existing + callers continue to compile unchanged. +* Structured ``KdfParameters`` provides compile-time type safety for KDF + configuration (salt, label, iteration count) where opaque byte spans did not. +* Dual overloads cleanly separate ephemeral key creation (RAII guard return) + from persistent slot writes (boolean return), with consistent calling convention. +* Span fields (peer public key, wrapped data, import key data, IV, AAD) + reference caller-owned memory — zero-copy for large buffers (PQC public keys + can reach 1–2 KB). + +**Negative:** + +* Six additional parameter struct types increase compilation includes if not + forward-declared. +* Callers must construct a params struct even for simple operations: + ``GenerateKeyParams{}.SetAlgorithm("AES-256")`` is more verbose than a + single factory call with a string argument. +* Span fields in params structs have lifetime constraints — referenced data + must outlive the struct. This is documented but not enforced at compile time. diff --git a/docs/crypto/architecture/dynamic_architecture.rst b/docs/crypto/architecture/dynamic_architecture.rst new file mode 100644 index 0000000..1afde3f --- /dev/null +++ b/docs/crypto/architecture/dynamic_architecture.rst @@ -0,0 +1,298 @@ +.. + # ============================================================================= + # C O P Y R I G H T + # ----------------------------------------------------------------------------- + # Copyright (c) 2026 by ETAS GmbH. All rights reserved. + # + # The reproduction, distribution and utilization of this file as + # well as the communication of its contents to others without express + # authorization is prohibited. Offenders will be held liable for the + # payment of damages. All rights reserved in the event of the grant + # of a patent, utility model or design. + # ============================================================================= + +.. _crypto_dynamic_architecture: + +API Dynamic Architecture +======================== + +.. comp_arc_dyn:: Dynamic View + :id: comp_arc_dyn__crypto__dynamic_view + :security: YES + :safety: QM + :status: invalid + :fulfils: + + Dynamic interactions for typical crypto operations. + +Typical Usage Flow +------------------ + +The standard interaction sequence for performing a cryptographic operation: + +.. uml:: typical_usage_sequence.puml + :align: center + :scale: 75 + +Pre-Deployed Key Flow (Slot-Direct Path) +----------------------------------------- + +Using a pre-deployed persistent key for encryption — the simplest path. +No ``LoadKey`` or guard needed; the context internally loads from the slot: + +.. + TODO: Removal all code blocks in this file. + It must be part of the examples over time. + +.. code-block:: cpp + + // 1. Create stack and context + CryptoStackConfig stack_config; + stack_config.SetConnectionEndpoint("unix:///var/run/crypto-daemon.sock"); + auto stack = CreateCryptoStack(stack_config).value(); + auto ctx = stack->CreateCryptoContext().value(); + + // 2. Resolve the pre-deployed key slot (no LoadKey needed) + auto slot = ctx->ResolveResource("PreDeployedSlot", ResourceType::kKeySlot).value(); + + // 3. Create cipher context — pass slot directly to SetKey + // The context factory internally loads key material from the slot. + CipherContextConfig enc_config; + enc_config.SetAlgorithm("AES-256-CBC").SetKey(slot).SetDirection(CipherDirection::kEncrypt); + auto enc = ctx->CreateCipherContext(enc_config).value(); + + // 4. Encrypt data + enc->Init(iv_span); + enc->Update(plaintext_span, ciphertext_span); + enc->Finalize(final_out_span); + + // 5. Context destruction releases the internally-loaded key material. + +Pre-Deployed Key Flow (Guard Path — Multi-Context Reuse) +--------------------------------------------------------- + +When the same key must be used across multiple contexts simultaneously, +use ``LoadKey()`` to get a ``CryptoResourceGuard``: + +.. code-block:: cpp + + // 1. Resolve slot and load key explicitly + auto slot = ctx->ResolveResource("PreDeployedSlot", ResourceType::kKeySlot).value(); + auto key_mgmt = ctx->CreateKeyManagementContext(KeyManagementContextConfig{}).value(); + auto key_guard = key_mgmt->LoadKey(slot).value(); + // key_guard is a CryptoResourceGuard wrapping type == kKey + + // 2. Query key slot metadata + auto info = key_mgmt->GetKeySlotInfo(slot).value(); + // info == {kOccupied, "AES-256", provider=2, compatible=[2,5]} + + // 3. Use the same loaded key in two contexts — implicit conversion + CipherContextConfig enc_config; + enc_config.SetAlgorithm("AES-256-CBC").SetKey(key_guard).SetDirection(CipherDirection::kEncrypt); + auto enc = ctx->CreateCipherContext(enc_config).value(); + + CipherContextConfig dec_config; + dec_config.SetAlgorithm("AES-256-CBC").SetKey(key_guard).SetDirection(CipherDirection::kDecrypt); + auto dec = ctx->CreateCipherContext(dec_config).value(); + + // 4. Use both contexts... + + // 5. ~key_guard releases the loaded key material via ReleaseResource() + +Ephemeral Key Generation and Use +--------------------------------- + +Generating an ephemeral key, using it, and optionally persisting: + +.. code-block:: cpp + + // Generate ephemeral key (returns CryptoResourceGuard) + auto key_mgmt = ctx->CreateKeyManagementContext(KeyManagementContextConfig{}).value(); + auto eph_key = key_mgmt->GenerateKey(GenerateKeyParams{} + .SetAlgorithm("AES-256") + .SetPermissions(KeyOperationPermission::kEncrypt | KeyOperationPermission::kDecrypt)).value(); + // eph_key is a CryptoResourceGuard wrapping: + // type == ResourceType::kKey + // persistence == ResourcePersistence::kEphemeral + // permitted operations == kEncrypt | kDecrypt + + // Use immediately in an operation — implicit conversion to const CryptoResourceId& + CipherContextConfig config; + config.SetAlgorithm("AES-256-GCM").SetKey(eph_key).SetDirection(CipherDirection::kEncrypt); + auto enc = ctx->CreateCipherContext(config).value(); + // ... encrypt data ... + + // Optionally persist for future use + auto target = ctx->ResolveResource("MyNewSlot", ResourceType::kKeySlot).value(); + key_mgmt->PersistKey(target, eph_key); // copy semantics: eph_key guard stays active + // Use target (kKeySlot handle) for all future durable operations + +Hashing Example +--------------- + +.. code-block:: cpp + + HashContextConfig hash_config; + hash_config.SetAlgorithm("SHA-256"); + auto hash = ctx->CreateHashContext(hash_config).value(); + + // Streaming + hash->Init(); + hash->Update(chunk1); + hash->Update(chunk2); + auto bytes_written = hash->Finalize(digest_out).value(); + + // Or single-shot + auto bytes = hash->SingleShot(input, digest_out).value(); + +Context Reuse via Reset() +------------------------- + +.. code-block:: cpp + + // Create the context once — expensive (factory + IPC) + HashContextConfig hash_config; + hash_config.SetAlgorithm("SHA-256"); + auto hash = ctx->CreateHashContext(hash_config).value(); + + // First message + hash->Init(); + hash->Update(message1); + auto n1 = hash->Finalize(digest1).value(); + + // Reuse — cheap (single IPC call, no factory overhead) + hash->Reset(); + + // Second message with the same context + hash->Init(); + hash->Update(message2); + auto n2 = hash->Finalize(digest2).value(); + + // Can also abort mid-stream and restart + hash->Init(); + hash->Update(partial_data); + hash->Reset(); // discard partial work + hash->Init(); + hash->Update(correct_data); + auto n3 = hash->Finalize(digest3).value(); + +Signing with PQC Algorithm +--------------------------- + +.. code-block:: cpp + + // Generate ML-DSA-65 key pair (returns CryptoResourceGuard) + auto key_mgmt = ctx->CreateKeyManagementContext(KeyManagementContextConfig{}).value(); + auto signing_key = key_mgmt->GenerateKey(GenerateKeyParams{}.SetAlgorithm("ML-DSA-65")).value(); + + // Sign data — implicit conversion passes guard to SetKey + SignContextConfig sign_config; + sign_config.SetAlgorithm("ML-DSA-65").SetKey(signing_key); + auto signer = ctx->CreateSignContext(sign_config).value(); + auto sig_size = signer->GetSignatureSize(); // ~3293 bytes for ML-DSA-65 + signer->Init(); + signer->Update(message); + auto sig_len = signer->SignFinalize(signature_buf).value(); + + // ~signing_key releases the ephemeral key pair + +Certificate Verification +------------------------- + +.. code-block:: cpp + + // Resolve certificate and verification trust store + auto cert = ctx->ResolveResource("DeviceCert", ResourceType::kCertSlot).value(); + auto anchor = ctx->ResolveResource("RootCA", ResourceType::kVerificationTrustStore).value(); + + // Verify using builder-style context + CertificateVerificationContextConfig verify_cfg; + auto verifier = ctx->CreateCertificateVerificationContext(verify_cfg).value(); + verifier->SetCertificate(cert); + verifier->SetVerificationTrustStore(anchor); + verifier->SetRevocationCheckPolicy(RevocationCheckPolicy::kCrlOnly); + auto result = verifier->Verify().value(); + + if (result == CertVerifyResult::kValid) { + // Extract public key for use — returns CryptoResourceGuard + auto [pub_key, alg] = cert_mgmt->LoadCertificatePublicKey(cert).value(); + // pub_key is a CryptoResourceGuard; use via implicit conversion to const CryptoResourceId& + // ~pub_key releases the ephemeral key when it goes out of scope + } + +Key Operation Permissions +-------------------------- + +Enforcing least-privilege on cryptographic keys: + +.. code-block:: cpp + + // 1. Generate an encryption-only key (cannot be used for signing) + auto key_mgmt = ctx->CreateKeyManagementContext(KeyManagementContextConfig{}).value(); + auto enc_key = key_mgmt->GenerateKey(GenerateKeyParams{} + .SetAlgorithm("AES-256") + .SetPermissions(KeyOperationPermission::kEncrypt | KeyOperationPermission::kDecrypt)).value(); + + // 2. Encryption works — key has kEncrypt permission + CipherContextConfig enc_config; + enc_config.SetAlgorithm("AES-256-CBC").SetKey(enc_key).SetDirection(CipherDirection::kEncrypt); + auto enc = ctx->CreateCipherContext(enc_config).value(); // OK + + // 3. Signing is rejected — key lacks kSign permission + SignContextConfig sign_config; + sign_config.SetAlgorithm("HMAC-SHA-256").SetKey(enc_key); + auto sign_result = ctx->CreateSignContext(sign_config); + // sign_result.error() == kKeyOperationNotPermitted + +.. code-block:: cpp + + // 4. Query permissions from a key slot + auto slot = ctx->ResolveResource("SigningKey", ResourceType::kKeySlot).value(); + auto slot_obj = ctx->GetKeySlotObject(slot).value(); + auto perms = slot_obj->GetPermittedOperations(); + + if (HasPermission(perms, KeyOperationPermission::kSign)) { + // Key slot allows signing + } + + // 5. Use composite presets for common patterns + auto auth_key = key_mgmt->GenerateKey(GenerateKeyParams{} + .SetAlgorithm("ECDSA-P256") + .SetPermissions(KeyOperationPermission::kAuthentication)).value(); + // auth_key can sign, verify, MAC, and agree — but cannot encrypt or wrap + +Operation Timeout Configuration +-------------------------------- + +Bounding all IPC calls with a per-call deadline for safety analysis: + +.. code-block:: cpp + + // 1. Stack-wide default: 500 ms per IPC call + CryptoStackConfig stack_config; + stack_config.SetConnectionEndpoint("unix:///var/run/crypto-daemon.sock") + .SetDefaultOperationTimeout(std::chrono::milliseconds{500}); + auto stack = CreateCryptoStack(stack_config).value(); + auto ctx = stack->CreateCryptoContext().value(); + + // 2. Per-context override: tighter 200 ms deadline for hashing + HashContextConfig hash_cfg; + hash_cfg.SetAlgorithm("SHA-256") + .SetOperationTimeout(std::chrono::milliseconds{200}); + auto hash = ctx->CreateHashContext(hash_cfg).value(); + + // Each Init(), Update(), Finalize() has its own 200 ms deadline. + hash->Init(); // ≤ 200 ms or kOperationTimedOut + hash->Update(chunk1); // ≤ 200 ms or kOperationTimedOut + auto result = hash->Finalize(digest_out); + if (!result.has_value()) { + // On timeout: context is in error state, must destroy and recreate. + // result.error() == kOperationTimedOut + } + + // 3. Disable timeout for slow PQC key generation on HSM + KeyManagementContextConfig keygen_cfg; + keygen_cfg.DisableTimeout(); // No deadline — HSM may take seconds + auto key_mgmt = ctx->CreateKeyManagementContext(keygen_cfg).value(); + auto pqc_key = key_mgmt->GenerateKey(GenerateKeyParams{}.SetAlgorithm("ML-KEM-768")).value(); + // PQC key generation can take seconds on hardware — no timeout error diff --git a/docs/crypto/architecture/index.rst b/docs/crypto/architecture/index.rst new file mode 100644 index 0000000..f4a3d07 --- /dev/null +++ b/docs/crypto/architecture/index.rst @@ -0,0 +1,194 @@ +.. + # ============================================================================= + # C O P Y R I G H T + # ----------------------------------------------------------------------------- + # Copyright (c) 2026 by ETAS GmbH. All rights reserved. + # + # The reproduction, distribution and utilization of this file as + # well as the communication of its contents to others without express + # authorization is prohibited. Offenders will be held liable for the + # payment of damages. All rights reserved in the event of the grant + # of a patent, utility model or design. + # ============================================================================= + +.. _component_architecture_template: + +Component Architecture +====================== + +.. document:: Crypto Architecture + :id: doc__crypto_architecture + :status: draft + :safety: QM + :security: NO + :realizes: wp__cmpt_request_dummy + +.. workproduct:: Component Request Dummy + :id: wp__cmpt_request_dummy + :status: draft + +Overview +-------- + +The ``score::mw::crypto`` module provides a provider-agnostic C++ (>=17) middleware +interface for cryptographic operations, key management, certificate lifecycle, and +shared memory allocation. It follows a client-daemon architecture where the +client library communicates with a daemon process over IPC. + +The API is organized around a single runtime handle type — +``CryptoResourceId`` — that encapsulates a daemon-assigned 64-bit identifier, +resource type, persistence semantics, and owning provider index. Applications +resolve human-readable string identifiers to ``CryptoResourceId`` handles once, +then use these compact numeric handles for all subsequent operations. + +Key design principles: + +- **Provider-agnostic**: Operations work identically across hardware (HSM, TEE) + and software (OpenSSL, SoftHSM) providers +- **PQC-ready**: ``AlgorithmId`` uses strings for extensibility, supporting + ML-KEM, ML-DSA, SLH-DSA, XMSS, LMS, and SHAKE algorithms +- **Ephemeral-by-default keys**: All key generation produces ephemeral keys; + explicit ``PersistKey()`` promotes to persistent storage +- **Zero-copy data plane**: Provider-compatible shared memory enables + zero-copy from application through daemon to crypto device +- **Backward-compatible extensibility**: All configs use default constructors + with fluent builders; new optional fields never break existing callers + +Requirements Linked to Component Architecture +--------------------------------------------- + +.. This section will be populated with requirement traceability links. + +.. needtable:: Overview of Component Requirements + :style: table + :columns: title;id + :filter: search("comp_arch_sta__archdes$", "fulfils_back") + :colwidths: 70,30 + +.. toctree:: + :maxdepth: 2 + :caption: Architecture Details + + api_architecture + api_description + dynamic_architecture + interfaces + provider_architecture + key_management_details + design_decisions + + +Static Architecture +------------------- + +The components are designed to cover the expectations from the feature architecture +(i.e. if already exists a definition it should be taken over and enriched). + +.. comp:: Crypto + :id: comp__crypto + :security: YES + :safety: QM + :status: invalid + :implements: + +.. image:: component_overview.png + :align: center + :scale: 75 + +.. TODO: Merge the below description into more appropriate sections when more details are available. + +Provider Layer +-------------- + +The provider layer decouples the daemon from concrete cryptographic library implementations +through two complementary abstractions: + +``IProvider`` + The single entry-point into one cryptographic back-end (e.g. OpenSSL, a PKCS#11 token). + Exposes ``GetCryptoHandlerFactory()``, ``GetKeyFactory()``, and ``GetKeySlotHandler()``. + Lifecycle is managed by ``ProviderManager``. + +``IProviderFactory`` + A pure-virtual factory interface with a single method + ``bool CreateAndRegister(ProviderManager&)``. + Concrete implementations encapsulate the construction and registration of one or more + related ``IProvider`` instances. Factories are registered externally + (daemon bootstrapper) via ``ProviderManager::RegisterFactory()`` and called in + registration order during ``ProviderManager::Initialize()``. + +``ScoreProviderFactory`` + Top-level factory for the **score interface family**. Accepts a vector of + ``ScoreProviderEntry`` configs (default: single OpenSSL entry). + ``CreateAndRegister()`` iterates configs and delegates to the appropriate + internal factory (e.g. ``OpenSSLProviderFactory``). + +``OpenSSLProviderFactory`` + Internal factory used by ``ScoreProviderFactory``. Constructs + ``score::openssl::OpenSSL`` and registers it as ``CryptoProviderType::SOFTWARE`` + under the ``common::kProviderNameOpenSSL`` name. No per-instance configuration required. + +``Pkcs11ProviderFactory`` + Accepts an injected ``std::vector`` via + ``SetTokenConfigs()`` (the acceptor side of the visitor pattern) or + through its explicit vector constructor. The daemon bootstrapper does + not build configs directly; it delegates to ``Pkcs11Config::Configure()``: + + .. code-block:: cpp + + config.GetPkcs11Config().PopulateDefaults(); + auto factory = std::make_unique(); + config.GetPkcs11Config().Configure(*factory); + manager.RegisterFactory(std::move(factory)); + + ``CreateAndRegister`` creates a single shared ``Pkcs11Module`` (so + ``C_Initialize`` is invoked exactly once regardless of token count), + then constructs and registers one ``Pkcs11Provider`` per entry as + ``CryptoProviderType::HARDWARE``. + + **Visitor pattern** — ``Pkcs11Config::Configure()`` is the visitor: + it iterates the ``Pkcs11TokenEntry`` list, converts each entry to a + ``Pkcs11ProviderConfig`` (filling labels, PIN, cleanup strategy), and + calls ``factory.SetTokenConfigs()``. + This keeps the ``Pkcs11TokenEntry → Pkcs11ProviderConfig`` conversion + entirely within the PKCS#11 subsystem + (``score/crypto/daemon/provider/pkcs11/pkcs11_token_config.*``). + + **Multi-token coexistence**: multiple ``Pkcs11TokenEntry`` entries in + ``Pkcs11Config`` produce one ``Pkcs11Provider`` per token. + All providers from the same factory share a single ``Pkcs11Module`` + (``C_Initialize`` / ``C_Finalize`` is called once), but each provider + maintains its own session pools, ``TokenAuthGuard``, and + ``Pkcs11KeyStore``. Login state and key registrations are fully isolated. + This design supports scenarios such as separate SoftHSM slots for + different trust domains within the same process. + + For session lifecycle details see + :ref:`pkcs11_session_management` in the key management details. + +``ProviderManager`` + Aggregates all registered providers and routes requests by ``ProviderId`` or + ``CryptoProviderType``. After all factories have been called, ``Initialize()`` + applies the daemon configuration and calls ``Initialize()`` on every provider. + +Dynamic Architecture +-------------------- + +The typical interaction sequence between Application, Client Library, and Crypto Daemon: + +.. uml:: typical_usage_sequence.puml + :align: center + :scale: 75 + +See :ref:`crypto_dynamic_architecture` for detailed usage flows including +pre-deployed key paths, ephemeral key generation, context reuse, PQC signing, +certificate verification, and timeout configuration. + +Interfaces +---------- + +See :ref:`crypto_interfaces` for the full interface descriptions. + +Design Decisions +---------------- + +See :ref:`crypto_design_decisions` for the full design decision records. diff --git a/docs/crypto/architecture/interfaces.rst b/docs/crypto/architecture/interfaces.rst new file mode 100644 index 0000000..31bd367 --- /dev/null +++ b/docs/crypto/architecture/interfaces.rst @@ -0,0 +1,418 @@ +.. + # ============================================================================= + # C O P Y R I G H T + # ----------------------------------------------------------------------------- + # Copyright (c) 2026 by ETAS GmbH. All rights reserved. + # + # The reproduction, distribution and utilization of this file as + # well as the communication of its contents to others without express + # authorization is prohibited. Offenders will be held liable for the + # payment of damages. All rights reserved in the event of the grant + # of a patent, utility model or design. + # ============================================================================= + +.. _crypto_interfaces: + +Interfaces +========== + +The public API surface is organized into the following interface groups: + +.. real_arc_int:: ICryptoStack + :id: real_arc_int__crypto__i_crypto_stack + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Application-level entry point for cryptographic operations. The + underlying daemon connection is managed internally and shared across + all ``ICryptoStack`` instances in the same process. Objects created + via this interface have independent lifetimes. Provides + ``CreateCryptoContext()`` and ``GetMemoryAllocator()``. + Created via ``CreateCryptoStack(CryptoStackConfig)`` where the + config supports ``SetConnectionEndpoint()`` and + ``SetDefaultOperationTimeout()`` for stack-wide per-IPC-call + deadline enforcement. + +.. real_arc_int:: CryptoResourceGuard + :id: real_arc_int__crypto__crypto_resource_guard + :security: YES + :safety: QM + :status: invalid + :language: cpp + + RAII guard for transient ``CryptoResourceId`` handles. Returned + by all resource-producing methods (``GenerateKey``, ``DeriveKey``, + ``AgreeKey``, ``UnwrapKey``, ``ImportKey``, ``LoadKey``, + ``LoadCertificatePublicKey``, ``ImportCrl``). Move-only to prevent + double-release. Provides ``Id()`` accessor, implicit conversion + to ``const CryptoResourceId&`` (so a guard can be passed directly to + any API accepting ``const CryptoResourceId&``, including ``SetKey``), + ``Release()`` for explicit synchronous release with ``Result`` + feedback, and ``IsActive()`` query. + + ``Release()`` provides the explicit ``Result`` path when + error handling is needed before destruction. It sends the release + IPC and auto-deactivates the guard on success. + + + Destructor is explicitly ``noexcept`` (MISRA C++:2023 Rule 18.4.1). + +.. real_arc_int:: ICryptoContext + :id: real_arc_int__crypto__i_crypto_context + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Factory and resource resolution interface for a crypto daemon session. + Resolves string resource identifiers to ``CryptoResourceId`` handles + via ``ResolveResource()`` and creates all operation-specific contexts. + Also provides typed object access. + Uses **forward declarations** for + all config, context, and object types — consumers include only + the specific headers they need. Provides + ``ResolveResource()`` for string-to-handle + resolution (with ACL), twelve ``Create[Op]Context()`` factory methods + (including ``CreateCertificateVerificationContext`` and + ``CreateCsrGenerationContext``), query methods + (``QueryCapabilities``, ``QueryProviderCompatibility``, + ``GetProviderInfo``), and typed object accessors + (``GetKeyObject``, ``GetKeySlotObject``, + ``GetCertificateObject``, ``GetCertSlotObject``, + ``GetProviderObject``). + +.. real_arc_int:: IMemoryAllocator + :id: real_arc_int__crypto__i_memory_allocator + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Zero-copy shared-memory allocator. Allocates provider-compatible + memory regions with optional type and provider hints. Provides + ``Allocate()`` for default and provider-compatible shared memory, + with per-application quota tracking via ``GetQuota()`` and + ``GetCurrentUsage()``. + +.. real_arc_int:: Streaming Context Hierarchy + :id: real_arc_int__crypto__streaming_contexts + :security: YES + :safety: QM + :status: invalid + :language: cpp + + ``IContext`` → ``IStreamingContext`` → ``IStreamingOutputContext`` + base hierarchy. All streaming methods (``Init()``, ``Update()``, + ``Reset()``, ``Finalize()``, ``GetOutputSize()``) are **protected** + in the base classes. Derived context interfaces selectively expose + them in their public sections via ``using``-declarations so that + each context presents a self-contained, user-friendly API surface. + + ``IStreamingContext::Init()`` accepts an + ``std::optional> iv`` parameter (default + ``std::nullopt``) to support algorithms that require an IV + (e.g. GMAC, AES-CBC). Contexts that do not use an IV (hash, + sign, verify) simply call ``Init()`` with the default. + + ``Reset()`` enables in-place context reuse after ``Finalize()`` + without a factory round-trip. Key/algorithm binding is preserved; + only streaming state is cleared. + + Concrete streaming contexts: ``IHashContext``, ``IMacContext``, + ``ICipherContext``, ``IAeadContext``, ``ISignContext``, + ``IVerifySignatureContext``, ``IRandomContext``. + +.. real_arc_int:: IKeyManagementContext + :id: real_arc_int__crypto__i_key_mgmnt_context + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Key lifecycle management with dual-overload design. Each + key-producing method (``GenerateKey``, ``DeriveKey``, ``AgreeKey``, + ``UnwrapKey``, ``ImportKey``) offers two overloads: + + - **Ephemeral overload** — takes a per-operation params struct, + returns ``CryptoResourceGuard`` wrapping an ephemeral key. + - **Direct-to-slot overload** — takes ``target_slot`` as the first + parameter, an optional ``public_slot`` (for asymmetric key generation), + followed by the params struct, returns ``Result``. + Key is generated/derived directly into the persistent slot(s). + + ``GenerateKey`` supports both symmetric (AES) and asymmetric (RSA, ECDH, + ML-DSA, etc.) algorithms. For asymmetric generation, the optional + ``public_slot`` parameter enables: + + - **Ephemeral public key** — omit ``public_slot``, derive public on-demand + via ``IPrivateKeyObject::GetPublicKey()`` + - **Persistent public key** — provide ``public_slot``, public key generated + directly into the slot + + Each operation's parameters are encapsulated in a dedicated + fluent-builder struct (``GenerateKeyParams``, ``DeriveKeyParams``, + ``AgreeKeyParams``, ``WrapKeyParams``, ``UnwrapKeyParams``, + ``ImportKeyParams``). The ``target_slot``-first convention is + consistent across all methods that accept a destination slot. + + ``DeriveKey`` and ``AgreeKey`` accept structured ``KdfParameters`` + for key derivation. Supported KDF algorithms: HKDF (RFC 5869), + TLS 1.2 PRF (RFC 5246), TLS 1.3 HKDF (RFC 8446), PBKDF2 (RFC 8018), + SP800-108. + + ``WrapKey`` and ``UnwrapKey`` accept wrapping metadata (IV, AAD, + wrapping algorithm) via their param structs, enabling authenticated + wrapping (e.g., AES-GCM key wrap). + + Also supports ``LoadKey`` (optional, advanced), ``ExportKey`` + (with format selection), ``ClearKey``, and ``GetKeySlotInfo``. + +.. real_arc_int:: ICertificateManagementContext + :id: real_arc_int__crypto__i_cert_mgmt_context + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Certificate lifecycle management — the certificate-domain mirror of + ``IKeyManagementContext``. Handles: ``ParseCertificate`` / + ``ParseCertificates`` (returns ``ICertificateObject::Uptr`` backed by + a daemon-assigned ephemeral handle), ``SaveCertificate(id, slot)`` + (copy semantics — object remains valid after persist), export + (``GetCertificateExportSize`` + ``ExportCertificate``), format + conversion (``GetConvertedCertificateSize`` + ``ConvertCertificateFormat``), + ``ClearCertificate``, ``GetCertificateSlotInfo``, CRL management + (``ImportCrl``, ``DeleteCrl``, ``DeleteExpiredCrls``, + ``DeleteExpiredCertificates``), + public key extraction (``LoadCertificatePublicKey`` — returns a + ``CryptoResourceGuard`` wrapping an ephemeral ``kKey`` resource, + following the same guard model as key-producing methods), and OCSP + request construction (``GetOcspRequestData``). + +.. real_arc_int:: ICertificateVerificationContext + :id: real_arc_int__crypto__i_cert_ver_context + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Builder-style certificate chain verification. Configures + certificate, chain, verification trust store, and revocation check + policy via fluent setters, then executes verification with ``Verify()``. + +.. real_arc_int:: ICsrGenerationContext + :id: real_arc_int__crypto__i_csr_gen_context + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Builder-style CSR generation. Configures subject key (as + ``CryptoResourceId``), signature algorithm, subject DN, and + SAN extensions via fluent setters, then generates with + ``Generate()``. + +.. real_arc_int:: Typed Object Hierarchy + :id: real_arc_int__crypto__typed_objects + :security: YES + :safety: QM + :status: invalid + :language: cpp + + ``ICryptoObject`` base with ``GetId()`` and ``GetType()`` + (both ``const noexcept``). All trivial accessors returning + POD, enum, ``bool``, or ``string_view`` values are marked + ``noexcept`` for exception-safety and optimiser hints. + Concrete typed objects: ``IKeyObject`` (algorithm, persistence, + exportability, key length, permitted operations), + ``ISymmetricKeyObject``, + ``IPublicKeyObject``, ``IPrivateKeyObject`` (with + ``GetPublicKey()`` to derive ephemeral public key from private), + ``IKeySlotObject`` (slot state, allowed algorithm, provider binding, + permitted operations), + ``ICertificateObject`` (subject, issuer, validity), + ``ICertSlotObject``, ``IProviderObject`` (type, name, + supported algorithms), ``ISecureObject``, ``IDataObject``. + Obtained via ``ICryptoContext`` accessors. + +.. real_arc_int:: CryptoResourceId + :id: real_arc_int__crypto__crypto_resource_id + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Compact runtime handle for a daemon-managed crypto resource. + Contains a daemon-assigned 64-bit identifier, resource type + (key, certificate, data, etc.), persistence semantics (transient + vs. persistent), and provider index. Obtained via + ``ICryptoContext::ResolveResource()`` for persistent resources or + from a ``CryptoResourceGuard`` for transient resources. Passed + directly to ``SetKey(const CryptoResourceId&)`` for the slot-direct + configuration path. + +.. real_arc_int:: BaseContextConfig + :id: real_arc_int__crypto__base_context_config + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Common fluent builder base for all operation context configuration + structs. Provides algorithm, provider, and timeout fields shared + across all contexts. Operation-specific subclasses: + ``HashContextConfig`` and ``RandomContextConfig`` (algorithm and + provider only); ``MacContextConfig`` (adds ``SetKey()``); + ``CipherContextConfig`` and ``AeadContextConfig`` (add ``SetKey()`` + and ``SetDirection()`` for encrypt/decrypt); ``SignContextConfig`` + and ``VerifySignatureContextConfig`` (add ``SetKey()``); + ``KeyManagementContextConfig`` (algorithm and provider for key + operations); ``CertificateContextConfig``, + ``CertificateVerificationContextConfig`` (adds + ``SetRevocationPolicy()``), and ``CsrGenerationContextConfig``. + Key-bearing configs accept a ``CryptoResourceId`` via ``SetKey()``; a + ``CryptoResourceGuard`` passes directly via its implicit conversion. + +.. real_arc_int:: KdfParameters + :id: real_arc_int__crypto__kdf_parameters + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Structured parameters for key derivation functions. + Contains typed fields: ``kdf_algorithm`` (``AlgorithmId``), + ``salt`` (fixed-capacity 128-byte array), ``label`` + (``FixedCapacityString<128>``), ``seed`` (fixed-capacity + 256-byte array), ``output_key_length``, and ``iteration_count`` + (both ``optional``). All fields have fluent setters. + Supports HKDF (RFC 5869), TLS 1.2 PRF (RFC 5246), TLS 1.3 + HKDF-Expand-Label (RFC 8446), PBKDF2 (RFC 8018), and + SP800-108 counter-mode KDF. Used by ``DeriveKeyParams`` and + optionally by ``AgreeKeyParams`` for combined agree+derive. + +.. real_arc_int:: Key Operation Parameter Structs + :id: real_arc_int__crypto__key_operation_params + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Per-operation fluent-builder parameter structs for + ``IKeyManagementContext`` methods. Each struct encapsulates the + inputs for one key lifecycle operation: + + - ``GenerateKeyParams`` — algorithm, permissions. + - ``DeriveKeyParams`` — source key, derived key algorithm, + ``KdfParameters``, permissions. + - ``AgreeKeyParams`` — private key, peer public key (span), + agreement algorithm, optional public key format, optional + derived key algorithm, optional ``KdfParameters``, permissions. + - ``WrapKeyParams`` — key to wrap, wrapping key, optional + wrapping algorithm, IV (span), AAD (span). + - ``UnwrapKeyParams`` — wrapped data (span), wrapping key, + inner key algorithm, optional wrapping algorithm, IV, AAD, + permissions. + - ``ImportKeyParams`` — key data (span), format, algorithm, + permissions. + + The ``target_slot`` destination is **not** part of any params + struct — it is expressed via the method overload signature + (``target_slot`` as the first parameter). + +.. real_arc_int:: IHashContext + :id: real_arc_int__crypto__i_hash_context + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Cryptographic hashing. Extends ``IStreamingOutputContext``; + exposes ``Init()``, ``Update()``, ``Reset()``, and ``Finalize()`` + from the base classes via ``using``-declarations plus ``SingleShot()`` + and ``GetDigestSize()``. ``GetOutputSize()`` is intentionally not + exposed — use ``GetDigestSize()`` instead. + +.. real_arc_int:: IMacContext + :id: real_arc_int__crypto__i_mac_context + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Message authentication code generation (HMAC, CMAC, GMAC, etc.). Extends + ``IStreamingOutputContext``; exposes ``Init()`` with the optional-IV + base signature, ``Update()``, ``Reset()``, and ``Finalize()`` via + ``using``-declarations; adds ``Verify()`` and ``GetMacSize()``. + ``GetOutputSize()`` is intentionally not exposed — use ``GetMacSize()`` + instead. + +.. real_arc_int:: ICipherContext + :id: real_arc_int__crypto__i_cipher_context + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Symmetric encryption and decryption. Extends + ``IStreamingOutputContext``; exposes ``Init()``, ``Reset()``, + ``Finalize()``, and ``GetOutputSize()`` from the base, plus + cipher-specific ``Update(input, output)`` and ``SingleShot()``. + ``Init()`` uses the base optional-IV signature (``span`` implicitly + converts to ``optional``); for IV-based modes (AES-CBC, + AES-CTR) an IV must be provided, for ECB ``std::nullopt`` is valid. + Direction (encrypt/decrypt) selected via + ``CipherContextConfig::SetDirection()``. + +.. real_arc_int:: IAeadContext + :id: real_arc_int__crypto__i_aead_context + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Authenticated encryption with associated data. Extends + ``IStreamingContext``; exposes ``Init()`` and ``Reset()`` from the + base. ``Init()`` uses the base optional-IV signature; a nonce/IV + must be provided (``nullopt`` returns ``kUnsupportedOperation``). + Adds ``UpdateAad()``, AEAD-specific ``Update(input, output)``, + dual finalization paths (``Finalize(output, tag)`` for encrypt, + ``VerifyAndFinalize(output, tag)`` for decrypt), and + ``GetTagSize()``. + +.. real_arc_int:: ISignContext + :id: real_arc_int__crypto__i_sign_context + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Digital signature generation. Extends ``IStreamingOutputContext``; + exposes ``Init()``, ``Update()``, and ``Reset()`` from the base. + Adds ``SignFinalize()``, ``SingleShot()``, and + ``GetSignatureSize()``. Base ``Finalize()`` and + ``GetOutputSize()`` are not exposed. + +.. real_arc_int:: IVerifySignatureContext + :id: real_arc_int__crypto__i_verify_sign_context + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Digital signature verification. Extends ``IStreamingContext``; + exposes ``Init()``, ``Update()``, and ``Reset()`` from the base. + Adds ``VerifyFinalize()`` and ``SingleShot()``. + +.. real_arc_int:: IRandomContext + :id: real_arc_int__crypto__i_random_context + :security: YES + :safety: QM + :status: invalid + :language: cpp + + Cryptographically secure random number generation. Provides + ``Generate()`` for filling a caller-supplied buffer with entropy + from the configured provider. diff --git a/docs/crypto/architecture/key_configuration_params.puml b/docs/crypto/architecture/key_configuration_params.puml new file mode 100644 index 0000000..b1fcdab --- /dev/null +++ b/docs/crypto/architecture/key_configuration_params.puml @@ -0,0 +1,107 @@ +@startuml score_crypto_api_key_configuration_objects + +title Score Crypto API — Key + +skinparam packageStyle rectangle + +' ============================================================ +' Key Operation Parameters +' ============================================================ +package "Key Operation Parameters" { + class KdfParameters <> { + + kdf_algorithm : AlgorithmId + + salt : array + + salt_length : size_t + + label : FixedCapacityString<128> + + seed : array + + seed_length : size_t + + output_key_length : optional + + iteration_count : optional + --- + + SetKdfAlgorithm(alg) : KdfParameters& + + SetSalt(data) : KdfParameters& + + SetLabel(lbl) : KdfParameters& + + SetSeed(data) : KdfParameters& + + SetOutputKeyLength(len) : KdfParameters& + + SetIterationCount(count) : KdfParameters& + } + + class GenerateKeyParams <> { + + algorithm : AlgorithmId + + permissions : KeyOperationPermission + --- + + SetAlgorithm(alg) : GenerateKeyParams& + + SetPermissions(perms) : GenerateKeyParams& + } + + class DeriveKeyParams <> { + + source_key : CryptoResourceId + + derived_key_algorithm : AlgorithmId + + kdf : KdfParameters + + permissions : KeyOperationPermission + --- + + SetSourceKey(id) : DeriveKeyParams& + + SetDerivedKeyAlgorithm(alg) : DeriveKeyParams& + + SetKdf(kdf) : DeriveKeyParams& + + SetPermissions(perms) : DeriveKeyParams& + } + + class AgreeKeyParams <> { + + private_key : CryptoResourceId + + peer_public_key : span + + agreement_algorithm : AlgorithmId + + public_key_format : optional + + derived_key_algorithm : optional + + kdf : optional + + permissions : KeyOperationPermission + --- + + SetPrivateKey(id) : AgreeKeyParams& + + SetPeerPublicKey(data) : AgreeKeyParams& + + SetAgreementAlgorithm(alg) : AgreeKeyParams& + + SetKdf(kdf) : AgreeKeyParams& + } + + class WrapKeyParams <> { + + key_to_wrap : CryptoResourceId + + wrapping_key : CryptoResourceId + + wrapping_algorithm : optional + + iv : span + + aad : span + --- + + SetKeyToWrap(id) : WrapKeyParams& + + SetWrappingKey(id) : WrapKeyParams& + + SetWrappingAlgorithm(alg) : WrapKeyParams& + + SetIv(data) : WrapKeyParams& + + SetAad(data) : WrapKeyParams& + } + + class UnwrapKeyParams <> { + + wrapped_data : span + + wrapping_key : CryptoResourceId + + inner_key_algorithm : AlgorithmId + + wrapping_algorithm : optional + + iv : span + + aad : span + + permissions : KeyOperationPermission + --- + + SetWrappedData(data) : UnwrapKeyParams& + + SetWrappingKey(id) : UnwrapKeyParams& + + SetInnerKeyAlgorithm(alg) : UnwrapKeyParams& + } + + class ImportKeyParams <> { + + key_data : span + + format : FormatType + + algorithm : AlgorithmId + + permissions : KeyOperationPermission + --- + + SetKeyData(data) : ImportKeyParams& + + SetFormat(fmt) : ImportKeyParams& + + SetAlgorithm(alg) : ImportKeyParams& + } + + DeriveKeyParams --> KdfParameters : uses + AgreeKeyParams --> KdfParameters : optionally uses +} + +@enduml diff --git a/docs/crypto/architecture/key_management_class_diagram.puml b/docs/crypto/architecture/key_management_class_diagram.puml new file mode 100644 index 0000000..8fe1890 --- /dev/null +++ b/docs/crypto/architecture/key_management_class_diagram.puml @@ -0,0 +1,561 @@ +@startuml key_management_class_diagram +!theme plain +skinparam linetype ortho +skinparam classAttributeIconSize 0 +skinparam classFontSize 12 +skinparam packageFontSize 14 + +title Key Management — Class Diagram + +' ==================================================================== +' COMPOSITION ROOT +' ==================================================================== +package "Composition Root" <> #FAFAFA { + class KeyManagementModule { + - m_slot_registry : SlotRegistry::Sptr + - m_service : KeyManagementService::Sptr + - m_provider_manager : ProviderManager::Sptr + -- + + {static} Create(IDataManager::Sptr, ProviderManager::Sptr, KeyConfig&) : Sptr + + GetSlotRegistry() : SlotRegistry::Sptr + + GetService() : KeyManagementService::Sptr + + GetProviderManager() : ProviderManager::Sptr + } + + interface IKeySlotCatalog <> { + + Load(registry : SlotRegistry&) + } + + class ConfigDrivenSlotCatalog <> { + - m_key_config : const KeyConfig& + -- + + Load(registry : SlotRegistry&) + } +} + +' ==================================================================== +' KEY MANAGEMENT SERVICE +' ==================================================================== +package "Core Service" <> #F0FFF0 { + + class KeyManagementService { + - m_data_manager : IDataManager::Sptr + - m_provider_manager : ProviderManager::Sptr + - m_slot_registry : SlotRegistry::Sptr + - m_registries : unordered_map + - m_slot_node_cache : unordered_map> + -- + + ResolveKeySlot(resource_name, client_id) : Expected + .. dedup: returns cached DataNodeId when same resource resolved twice .. + + RegisterKeyMaterial(params, key_handler) : Expected + + LoadOrShare(params, slot_handler, slot_config) : Expected + + ReleaseKeyMaterial(client_id, ref_node_id) : Expected + + CleanupClient(client_id) + + BindKeyToContext(client_id, context_node_id, key_node_id, target_provider_id) : Expected + + ResolveTargetProvider(client_id, type, key_node_id) : Expected + + ResolveSlotForOperation(client_id, slot_node_id) : Expected + + GetSlotRegistry() : SlotRegistry::Sptr + + GetProviderManager() : ProviderManager::Sptr + + GetDataManager() : IDataManager::Sptr + - GetProviderRegistry(provider_id) : KeyRegistry& + } + + struct SlotResolution { + + config : const KeySlotConfig* + + handle : SlotHandle + } + + struct KeyBindingResult { + + key_handler : IKeyHandler::Sptr + + resolved_node_id : DataNodeId + } + + struct KeyDataNodeResult { + + node_id : DataNodeId + + key_node : shared_ptr + } + + note right of KeyManagementService + **Orchestration only** + DataNode lifecycle, slot resolution, + context key binding, provider routing, + cross-crash cleanup. + No typed crypto methods. + end note +} + +' ==================================================================== +' KEY REGISTRY +' ==================================================================== +package "Key Registry" <> #F0FFF0 { + class KeyRegistry { + - m_mutex : mutex + - m_keys : unordered_map> + - m_slot_to_id : unordered_map + - m_next_id : KeyRegistryId + -- + + RegisterSlotKey(slot_handle, key_entry) : KeyRegistryId + + RegisterEphemeralKey(key_entry) : KeyRegistryId + + FindBySlot(slot_handle) : shared_ptr + + FindById(id) : shared_ptr + + Unregister(id) : bool + + CleanupClient(client_id) + + Size() : size_t + } +} + +' ==================================================================== +' KEY MANAGEMENT CORE +' ==================================================================== +package "Slot Management" <> #F0F8FF { + + class SlotRegistry { + - m_registry : vector + - m_name_index : unordered_map + - m_mutex : mutex + -- + + RegisterSlot(config : KeySlotConfig) : SlotHandle + + ResolveSlot(slot_name, client_id) : Expected + + ResolveAppResource(app_resource_id, client_id) : Expected + + RegisterAppResource(uid, app_resource_id, slot_name) + + GetConfig(handle) : Expected + + GetPrimaryProviderId(handle) : Expected + + IsProviderAllowedForSlot(handle, provider_id) : Expected + + ResolveProviderIds(provider_manager) + + GetSlotCount() : size_t + } + + class AccessPolicyEnforcer <> { + + {static} CheckSlotAccess(config, client_id) : Expected + + {static} CheckWritePermission(config, client_id) : Expected + + {static} CheckOperationPermission(config, required) : Expected + + {static} CheckProviderAccess(config, provider_id, is_write) : Expected + + {static} Authorize(config, client_id, required) : Expected + } + + interface IKeyFactory <> { + + GenerateKey(request) : Expected + + ImportKey(request) : Expected + + DeriveKey(request) : Expected + + AgreeKey(request) : Expected + + WrapKey(request) : Expected + + UnwrapKey(request) : Expected + + ExportKey(handle, format) : Expected + .. default implementations return kNotSupported .. + } + + interface IKeySlotHandler <> { + + LoadKey(config) : Expected + + GetSlotState(config) : Expected + + GetSlotInfo(config) : Expected + + StoreKey(config, handler) : Expected + + ClearSlot(config) : Expected + .. StoreKey/ClearSlot default → kNotSupported .. + } + + interface IKeyHandler <> { + + GetHandle() : const ProviderKeyHandle& + + GetProviderId() : const ProviderId& + + Release() : Expected + + Export() : Expected + .. Release() is idempotent; second call is no-op .. + } + + class FileBackedSlotHandler { + - m_key_handler : IKeyHandler::Sptr + -- + + LoadKey(config) : Expected + + GetSlotState(config) : Expected + + GetSlotInfo(config) : Expected + } +} + +' ==================================================================== +' SLOT DEPLOYMENT (slot/deployment/) +' ==================================================================== +package "Slot Deployment" <> #E8F0FE { + + struct SlotDeploymentInfo { + + metadata : unordered_map + + key_properties : unordered_map + .. metadata_keys: availability, provisioned_at, .. + .. update_counter, hash, kek.keyslotname, kek.algo, kek.iv .. + .. deployment_keys: key_path, key_format, key, .. + .. pkcs11.label, pkcs11.object_id, pkcs11.object_class, .. + .. tee.key_id, psa.key_id .. + } + + interface IDeploymentLoader <> { + + Load(path : string) : Expected + .. path is pre-validated by the façade .. + } + + interface IDeploymentWriter <> { + + Write(path : string, info : SlotDeploymentInfo) : Expected + .. path is pre-validated by the façade .. + } + + class DeploymentLoader <> { + + {static} Load(path, format) : Expected + .. 1. IsDeploymentPathSafe(path) .. + .. 2. if format=="kv" → KvDeploymentLoader{}.Load(path) .. + .. 3. add branch here for each future format .. + } + + class DeploymentWriter <> { + + {static} Write(path, format, info) : Expected + .. 1. IsDeploymentPathSafe(path) .. + .. 2. if format=="kv" → KvDeploymentWriter{}.Write(path, info) .. + } + + class KvDeploymentLoader { + + Load(path : string) : Expected + .. parses [metadata] / [key] key=value sections .. + .. blank lines and # comments ignored .. + } + + class KvDeploymentWriter { + + Write(path : string, info : SlotDeploymentInfo) : Expected + .. writes [metadata] then [key] sections .. + .. opens with ios::trunc .. + } +} + +' ==================================================================== +' OPENSSL IMPLEMENTATIONS +' ==================================================================== +package "OpenSSL Provider" <> #FFFDE7 { + class OpenSslKeyFactory { + + GenerateKey(request) : Expected + .. RAND_bytes → heap buffer → OpenSslKeyHandler .. + + ImportKey(request) : Expected + .. memcpy to heap buffer .. + - DetermineKeySize(algorithm) : size_t + } + + class OpenSslKeyHandler { + - m_key_bytes : vector + - m_handle : ProviderKeyHandle + - m_released : bool + -- + + GetHandle() : const ProviderKeyHandle& + + GetProviderId() : const ProviderId& + + Release() : Expected + .. OPENSSL_cleanse + clear(); idempotent .. + + Export() : Expected + + GetRawKeyBytes(out_size) : const uint8_t* + .. direct access for zero-copy MAC key binding .. + } +} + +' ==================================================================== +' PKCS#11 IMPLEMENTATIONS +' ==================================================================== +package "PKCS#11 Provider" <> #E8F5E9 { + + class Pkcs11Config { + - m_tokens : vector + -- + + AddTokenEntry(entry) + + GetTokenEntries() : const vector& + + PopulateDefaults() + .. no-op when entries already present .. + + Configure(factory : Pkcs11ProviderFactory&) + .. visitor: converts TokenEntry → ProviderConfig .. + .. calls factory.SetTokenConfigs() .. + } + + class Pkcs11ProviderFactory { + - m_injected_configs : vector + -- + + Pkcs11ProviderFactory() + + Pkcs11ProviderFactory(configs : vector) + + SetTokenConfigs(configs : vector) + .. acceptor side of Pkcs11Config visitor .. + + CreateAndRegister(manager) : bool + .. shared Pkcs11Module; one Pkcs11Provider per config .. + } + + class Pkcs11KeyFactory { + + GenerateKey(request) : Expected + .. C_GenerateKey → session key (CKA_TOKEN=false) .. + + ImportKey(request) : Expected + .. C_CreateObject .. + } + + class Pkcs11KeyHandler { + - m_key_store : weak_ptr + - m_handle : ProviderKeyHandle + - m_released : atomic + -- + + GetHandle() : const ProviderKeyHandle& + + GetProviderId() : const ProviderId& + + Release() : Expected + .. store->Release(opaque_id) .. + + Export() : Expected + .. always kNotPermitted .. + + GetSessionKey() : pair + } + + class Pkcs11KeySlotHandler { + + LoadKey(config) : Expected + .. C_FindObjects by label/object_id → Pkcs11KeyHandler .. + + GetSlotState(config) : Expected + + GetSlotInfo(config) : Expected + } + + class Pkcs11KeyStore { + - m_map_mutex : mutex + - m_map : unordered_map> + -- + + Register(session, object, algo, size) : ProviderKeyHandle + + RegisterTokenObject(object, algo, size) : ProviderKeyHandle + + Lookup(opaque_id) : pair + + Release(opaque_id, handle) : Expected + .. session keys: C_DestroyObject on release .. + .. token objects: map entry removed, object preserved .. + } +} + +' ==================================================================== +' PROVIDER HANDLER BASE +' ==================================================================== +package "Provider Handler" <> #FCE4EC { + + abstract class Handler <> { + + InitializeContext(init_params) : Expected + + Execute(operationId, request) : Expected + + Reset() : Expected + } + + struct InitializationParams { + + client_id : uint64_t + + context_node_id : uint64_t + + provider_id : ProviderId + + key_node_id : uint64_t + + bound_key_handler : const IKeyHandler* + .. non-owning; valid for InitializeContext() only .. + + context_creation_params : RequestParameters + .. full CTX_CREATE wire params [0..N]; handlers read .. + .. MAC-specific fields (e.g. operation_mode at [4]) here .. + } + + abstract class MacHandler { + # m_algorithm : AlgorithmId + # m_state : StreamOperationState + -- + + GetMacSize() : size_t + + StartMac(initialDataOrIV) : Expected + + UpdateMac(request) : Expected + + FinalizeMac(output, finalData) : Expected + + VerifyMac(expectedTag) : Expected + } + + class OpenSslHmacHandler <> { + - m_mac : EVP_MAC* + - m_ctx : EVP_MAC_CTX* + - m_output_buffer : vector + - m_init_params : InitializationParams + -- + + InitializeContext(params) : Expected + .. downcasts bound_key_handler → OpenSslKeyHandler .. + .. calls GetRawKeyBytes() + EVP_MAC_init .. + + Execute(opId, request) : Expected + + UpdateMac(request) : Expected + + FinalizeMac(output, finalData) : Expected + + VerifyMac(expectedTag) : Expected + + StartMac(initialDataOrIV) : Expected + + {static} IsAlgorithmSupported(algorithm) : bool + } +} + +' ==================================================================== +' KEY MANAGEMENT EXECUTOR +' ==================================================================== +package "Key Mgmt Executor" <> #FFF8E7 { + + class KeyManagementExecutor { + - m_factory : shared_ptr + - m_slot_handler : shared_ptr + - m_service : shared_ptr + -- + + KeyManagementExecutor(factory, slot_handler, service) + + Execute(ctx, operationId, request) : Expected + - HandleGenerate(ctx, request) : Expected + .. factory.GenerateKey() → service.RegisterKeyMaterial() .. + - HandleLoad(ctx, request) : Expected + .. service.ResolveSlotForOperation() → service.LoadOrShare() .. + - HandleRelease(client_id, request) : Expected + .. service.ReleaseKeyMaterial() .. + - HandleSlotInfo(ctx, request) : Expected + .. ResolveSlot + slot_handler.GetSlotInfo() .. + } + + class Pkcs11MacExecutor { + - m_module : const Pkcs11Module& + - m_functionList : const CK_FUNCTION_LIST* + -- + + Execute(ctx, action, request, currentState, nextState) : Expected + + Abort(session, operation_mode) + - HandleInit(ctx, use_verify, currentState, nextState) + - HandleUpdate(ctx, use_verify, currentState, nextState, request) + - HandleFinal(ctx, currentState, nextState, request) + - HandleVerify(ctx, use_verify, currentState, nextState, request) + - EnsureInitialized(session, mechanism, key_object, use_verify, currentState) + .. calls C_SignInit/C_VerifyInit only when STREAM_INIT .. + - ExecuteSignInit / ExecuteSignUpdate / ExecuteSignFinal + - ExecuteVerifyInit / ExecuteVerifyUpdate / ExecuteVerifyFinal + - ExecuteSignSingleShot / ExecuteVerifySingleShot / ExecuteSignVerify + } + + note right of KeyManagementExecutor + Each provider handler owns one instance + and calls Execute() with its own deps + injected at construction time. + Key operations dispatched by operationAction. + end note +} + +' ==================================================================== +' DATA NODES +' ==================================================================== +package "Data Nodes" <> #FFF0F0 { + + class KeyEntry { + - m_handler : IKeyHandler::Sptr + - m_provider_id : ProviderId + - m_slot_handle : SlotHandle + - m_ref_mutex : mutex + - m_ref_count : atomic + - m_referencing_clients : vector + -- + + GetHandle() : const ProviderKeyHandle& + + GetKeyHandler() : IKeyHandler::Sptr + + GetProviderId() : const ProviderId& + + AddRef(client_id) + + Release(client_id) : bool + .. returns true when ref_count reaches 0 .. + + GetRefCount() : uint32_t + .. ~KeyEntry() → handler->Release() (zeroize) .. + } + + class KeySlotDataNode { + - m_slot_handle : SlotHandle + - m_slot_registry : SlotRegistry::Sptr + -- + + GetSlotHandle() : SlotHandle + + GetConfig() : Expected + + GetSlotRegistry() : SlotRegistry::Sptr + .. exclusiveAccess = false .. + .. ~24 bytes; no KeySlotConfig copy .. + } + + class KeyDataNode { + - m_key_entry : shared_ptr + - m_registry_id : KeyRegistryId + - m_client_id : ClientId + - m_on_last_release : UnregisterCallback + -- + + GetKeyEntry() : shared_ptr + + GetRegistryId() : KeyRegistryId + .. ctor: key_entry->AddRef(client_id) .. + .. dtor: key_entry->Release(client_id) .. + .. if last ref → on_last_release(registry_id) .. + .. exclusiveAccess = false .. + } + + class ContextDataNode { + - m_handler : shared_ptr + - m_algorithm : string + -- + + ContextDataNode(handler, algorithm) + + GetHandler() : shared_ptr + + GetAlgorithm() : const string& + .. exclusiveAccess = true .. + .. child KeyDataNodes cascade-deleted on CTX_CLOSE .. + } +} + +' ==================================================================== +' MEDIATOR INTEGRATION +' ==================================================================== +package "Mediator" <> #F8F0FF { + class MediatorImpl { + - m_data_manager : IDataManager::Sptr + - m_provider_manager : ProviderManager::Sptr + - m_km_service : KeyManagementService::Sptr + - m_resource_resolvers : map + -- + + processRequest(ControlRequest) : ControlResponse + - HandleSingleOperation(...) + - HandleMediatorOperation(...) + .. CTX_CREATE → HandleContextCreationOperation .. + .. CTX_CLOSE → deleteNode cascade .. + .. RESOLVE_RESOURCE → slot resolver .. + - HandleContextCreationOperation(...) + .. ResolveTargetProvider → GetProvider .. + .. CreateHandler → CreateContextDataNode .. + .. BindKeyToContext → InitializeContext .. + - ForwardSingleOperation(...) + .. look up ContextDataNode → handler->Execute() .. + } +} + +' ==================================================================== +' RELATIONSHIPS +' ==================================================================== +KeyManagementModule --> KeyManagementService : creates +KeyManagementModule --> SlotRegistry : creates +KeyManagementModule ..> IKeySlotCatalog : loads slots via + +ConfigDrivenSlotCatalog ..|> IKeySlotCatalog + +KeyManagementService --> SlotRegistry : uses +KeyManagementService --> KeyRegistry : owns per provider +KeyManagementService --> KeyEntry : creates via RegisterKeyMaterial +KeyManagementService --> KeyDataNode : creates via LoadOrShare / RegisterKeyMaterial +KeyManagementService --> KeySlotDataNode : creates via ResolveKeySlot, reads via ResolveSlotForOperation +KeyManagementService ..> KeyBindingResult : returns from BindKeyToContext +KeyManagementService ..> SlotResolution : returns from ResolveSlotForOperation +KeyManagementService ..> KeyDataNodeResult : returns from RegisterKeyMaterial + +KeyManagementExecutor --> KeyManagementService : m_service +KeyManagementExecutor --> IKeyFactory : m_factory +KeyManagementExecutor --> IKeySlotHandler : m_slot_handler + +FileBackedSlotHandler ..|> IKeySlotHandler +Pkcs11KeySlotHandler ..|> IKeySlotHandler + +DeploymentLoader ..> IDeploymentLoader : dispatches to +DeploymentWriter ..> IDeploymentWriter : dispatches to +KvDeploymentLoader ..|> IDeploymentLoader +KvDeploymentWriter ..|> IDeploymentWriter +DeploymentLoader ..> SlotDeploymentInfo : returns +IDeploymentLoader ..> SlotDeploymentInfo : returns + +FileBackedSlotHandler --> DeploymentLoader : Load(deployment_path, format) +Pkcs11KeySlotHandler --> DeploymentLoader : Load(deployment_path, format) + +OpenSslKeyFactory ..|> IKeyFactory +Pkcs11KeyFactory ..|> IKeyFactory +OpenSslKeyHandler ..|> IKeyHandler +Pkcs11KeyHandler ..|> IKeyHandler +Pkcs11KeyHandler --> Pkcs11KeyStore : weak_ptr +Pkcs11KeyFactory --> Pkcs11KeyStore : shared_ptr + +MacHandler --|> Handler +OpenSslHmacHandler --|> MacHandler +OpenSslHmacHandler ..> OpenSslKeyHandler : downcast from bound_key_handler\n(type-safe via ProviderId check) + +KeyEntry --> IKeyHandler : owns (sole owner) +KeyDataNode --> KeyEntry : shared_ptr; ctor AddRef / dtor Release +KeyRegistry --> KeyEntry : shared ownership + +ContextDataNode --> Handler : owns +ContextDataNode ..> KeyDataNode : parent — child KeyDataNodes\ncascade-deleted on CTX_CLOSE + +MediatorImpl --> KeyManagementService : ResolveKeySlot, BindKeyToContext, ResolveTargetProvider +MediatorImpl --> ContextDataNode : creates, looks up +MediatorImpl --> InitializationParams : builds for InitializeContext + +@enduml diff --git a/docs/crypto/architecture/key_management_details.rst b/docs/crypto/architecture/key_management_details.rst new file mode 100644 index 0000000..9bca9ed --- /dev/null +++ b/docs/crypto/architecture/key_management_details.rst @@ -0,0 +1,686 @@ +.. + # ============================================================================= + # C O P Y R I G H T + # ----------------------------------------------------------------------------- + # Copyright (c) 2026 by ETAS GmbH. All rights reserved. + # + # The reproduction, distribution and utilization of this file as + # well as the communication of its contents to others without express + # authorization is prohibited. Offenders will be held liable for the + # payment of damages. All rights reserved in the event of the grant + # of a patent, utility model or design. + # ============================================================================= + +.. _crypto_key_management_details: + +Key Management Architecture +=========================== + +This document explains the key management subsystem of ``score::mw::crypto`` +in detail: how keys come into being, how they are stored, how their lifetime +is managed across multiple clients, and how they are bound to a cryptographic +operation context. A MAC operation is used as the running example because it +touches every layer of the subsystem. + +.. contents:: + :local: + :depth: 2 + +Architecture Overview +--------------------- + +The daemon key management stack has four layers: + +.. list-table:: Key Management Layers + :header-rows: 1 + :widths: 20 80 + + * - Layer + - Responsibility + * - **Interface layer** (``interfaces/``) + - Pure abstractions: ``IKeyFactory``, ``IKeyHandler``, ``IKeySlotHandler``, + ``key_types.hpp``, ``key_management_operations.hpp``. No provider-specific code. + * - **Slot registry layer** (``slot/``) + - ``KeySlotConfig`` — per-slot algorithm, provider IDs, access policy, provider + parameters. ``SlotRegistry`` — central slot registry with UID access control + and atomic usage counting. ``AccessPolicyEnforcer`` — all access decisions + centralized in one place. ``DeploymentLoader`` / ``DeploymentWriter`` — façades + that delegate to format-specific implementations under ``slot/deployment/`` + (see `Deployment Descriptor`_ below). + * - **Core orchestration layer** (``core/``) + - ``KeyRegistry`` — per-provider deduplication map of live key nodes. + ``KeyManagementService`` — DataNode lifecycle (register / load-or-share / bind / + release), provider routing, and crash cleanup. + * - **Data-node layer** (``nodes/``) + - ``KeyEntry`` — sole owner of an ``IKeyHandler``; reference-counted across + clients. ``KeyDataNode`` — lightweight RAII guard in the client tree; + holds a ``shared_ptr`` and triggers release on destruction. + ``KeySlotDataNode`` — ~24-byte resolved-slot reference, no config copy. + +The composition root ``KeyManagementModule`` wires these layers at daemon startup and +injects the shared ``KeyManagementService`` into every provider. + +Class Diagram +------------- + +The following diagram shows the key class relationships: + +.. uml:: key_management_class_diagram.puml + :align: center + :caption: Key Management — Class Diagram + :alt: UML class diagram for the key management subsystem. + +Sequence Diagrams +----------------- + +The following sequence diagram set illustrates daemon startup, slot resolution, +key generation, key load with deduplication, MAC context creation with key +binding, MAC streaming, and crash cleanup: + +.. uml:: key_management_sequence_diagrams.puml + :align: center + :caption: Key Management — Sequence Diagrams + :alt: UML sequence diagrams for key management operations. + +Key Lifecycle +------------- + +Keys enter the daemon through either of two paths and leave via an explicit +release or client-crash cleanup. + +Slot resolution +~~~~~~~~~~~~~~~ + +Before a key can be loaded the application resolves a slot name to a +``DataNodeId``: + +1. The client sends ``RESOLVE_RESOURCE(slot_name, kKeySlot)`` to the mediator. +2. ``MediatorImpl`` calls ``SlotRegistry::ResolveAppResource(slot_name, + client_id)`` which checks the UID-to-resource map and delegates to + ``ResolveSlot``. ``AccessPolicyEnforcer::CheckSlotAccess`` validates that + ``client_id`` is in the slot's ``allowed_uids`` list. +3. A ``KeySlotDataNode(slot_handle, slot_registry)`` is stored in the + ``DataManager`` under the client's session node. The node is small (~24 + bytes) — it holds only the ``SlotHandle`` index and a shared pointer to the + ``SlotRegistry``; no ``KeySlotConfig`` is copied. +4. The node's ``DataNodeId`` is returned to the client as the slot handle for + subsequent operations. + +Key generation (ephemeral) +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When the client calls ``KeyManagementContext::GenerateKey`` the IPC path is: + +1. ``KEY_GENERATE`` reaches ``MediatorImpl::ForwardSingleOperation`` and is + dispatched to the ``ContextDataNode``'s handler + (``OpenSslKeyManagementHandler`` or ``Pkcs11KeyManagementHandler``). +2. The handler delegates to its ``KeyManagementExecutor::HandleGenerate``. +3. ``IKeyFactory::GenerateKey(KeyGenerationRequest)`` is called: + + - **OpenSSL**: ``RAND_bytes`` → heap-allocated buffer → + ``OpenSslKeyHandler`` + - **PKCS#11**: ``C_GenerateKey`` with ``CKA_TOKEN=false`` (session key) → + ``Pkcs11KeyHandler`` + +4. ``KeyManagementService::RegisterKeyMaterial`` is called with the new + handler: + + - A ``KeyEntry`` is created (owns the ``IKeyHandler``). + - ``KeyRegistry::RegisterEphemeralKey`` assigns a ``KeyRegistryId`` and + stores the node. + - A ``KeyDataNode`` is added under the calling context node in the + ``DataManager``. Its constructor calls ``key_entry->AddRef(client_id)`` + (ref-count = 1). + +5. The ``DataNodeId`` of the ``KeyDataNode`` is returned to the client as a + ``CryptoResourceGuard`` wrapping a ``kKey`` resource. + +Loading a pre-deployed slot key (with deduplication) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When the client calls ``KeyManagementContext::LoadKey(slot_node_id)``: + +1. ``KEY_LOAD`` is forwarded to the provider handler's executor. +2. ``KeyManagementExecutor::HandleLoad`` calls + ``KeyManagementService::ResolveSlotForOperation(client_id, slot_node_id)`` + which returns ``SlotResolution{config*, slot_handle}``. +3. ``KeyManagementService::LoadOrShare`` is called: + + - ``KeyRegistry::FindBySlot(slot_handle)`` checks whether the slot is + already loaded. + - **Already loaded**: the existing ``KeyEntry`` is reused; a new + ``KeyDataNode`` is added to the current client's tree and its constructor + calls ``key_entry->AddRef(client_id)``. No provider I/O occurs. + - **First load**: ``IKeySlotHandler::LoadKey(*config)`` is called (file + read or PKCS#11 ``C_FindObjects``), a new ``KeyEntry`` is created, + ``KeyRegistry::RegisterSlotKey`` stores it, and a ``KeyDataNode`` is + added as before. + +This deduplication is critical for PKCS#11 tokens, where loading the same +token object twice would either produce a redundant handle or fail. + +Key release +~~~~~~~~~~~ + +Explicit: + The client calls ``CryptoResourceGuard::~CryptoResourceGuard`` on the key + guard, which sends ``KEY_RELEASE``. The executor calls + ``KeyManagementService::ReleaseKeyMaterial(client_id, ref_node_id)`` → + ``DataManager::deleteNode`` → ``~KeyDataNode()`` → + ``key_entry->Release(client_id)``. If that was the last reference, the + unregister callback fires: ``KeyRegistry::Unregister(registry_id)`` drops + the registry's ``shared_ptr``, destroying the ``KeyEntry``: + ``IKeyHandler::Release()`` zeroizes key material. + +Implicit (context close): + ``CTX_CLOSE`` calls ``DataManager::deleteNode`` on the + ``ContextDataNode``. All child ``KeyDataNode`` entries (bound via + ``BindKeyToContext``) are cascade-deleted in the same call, triggering the + same chain. + +Client crash: + ``DataManager::deleteClientNodes(client_id)`` performs a post-order tree + traversal, deleting all nodes in the client's subtree (cascade destruction + of ``KeyDataNode`` entries). ``KeyManagementService::CleanupClient`` + is also called as a safety net — it iterates every ``KeyRegistry`` and + calls ``Release(client_id)`` on every ``KeyEntry`` that still references + that client. + +MAC Operation Example +--------------------- + +This section traces a complete HMAC-SHA256 operation from application code +down to the OpenSSL ``HMAC_CTX``. + +Step 1 — Resolve the key slot +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: cpp + + // Application code (client side) + auto stack = CreateCryptoStack(stack_config).value(); + auto ctx = stack->CreateCryptoContext().value(); + + // Resolve the pre-deployed HMAC key slot + auto slot = ctx->ResolveResource("HmacProductionSlot", + ResourceType::kKeySlot).value(); + // slot is a CryptoResourceGuard wrapping type=kKeySlot + +**Daemon side**: ``RESOLVE_RESOURCE("HmacProductionSlot", kKeySlot)`` → +``SlotRegistry::ResolveAppResource`` → ``AccessPolicyEnforcer::CheckSlotAccess`` +→ ``KeySlotDataNode`` stored in ``DataManager`` → ``slot_node_id`` returned. + +Step 2 — Load the key +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: cpp + + // Create a key management context and load the key explicitly. + // This step allows reuse of the same key across multiple MAC contexts. + auto km = ctx->CreateKeyManagementContext(KeyManagementContextConfig{}).value(); + auto key_guard = km->LoadKey(slot).value(); + // key_guard wraps type=kKey, persistence=kPersistent + +**Daemon side**: + +1. ``KEY_LOAD`` is forwarded to ``OpenSslKeyManagementHandler::Execute`` (OpenSSL + provider context). +2. ``KeyManagementExecutor::HandleLoad`` → ``ResolveSlotForOperation`` → + ``LoadOrShare``: + + - ``KeyRegistry::FindBySlot`` returns ``nullptr`` (first load). + - ``FileBackedSlotHandler::LoadKey(config)`` reads the key file at the path + stored in ``config.deployment_path`` (via ``DeploymentLoader``) and + constructs an ``OpenSslKeyHandler``. + - ``KeyRegistry::RegisterSlotKey`` stores the new ``KeyEntry`` + (``ref_count = 0``). + - ``CreateKeyDataNode`` creates a ``KeyDataNode`` under the key-management + context node; its constructor calls ``key_entry->AddRef(client_id)`` + and sets ``ref_count = 1``. + +3. ``key_ref_node_id`` returned to client → ``CryptoResourceGuard``. + +Step 3 — Create the MAC context (context creation with key binding) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: cpp + + MacContextConfig mac_cfg; + mac_cfg.SetAlgorithm("HMAC-SHA256").SetKey(key_guard); + auto mac = ctx->CreateMacContext(mac_cfg).value(); + +**Daemon side** — ``CTX_CREATE(type="MAC", algo="HMAC-SHA256", +provider=SOFTWARE, key_node_id=key_ref_node_id)``: + +1. **Provider routing**: ``KeyManagementService::ResolveTargetProvider( + client_id, SOFTWARE, key_ref_node_id)`` examines the ``KeyEntry``'s + ``provider_id`` (``"openssl"``). Since the key lives in OpenSSL and the + requested type is ``SOFTWARE``, the resolved provider is ``"openssl"``. + +2. **Handler creation**: ``ProviderManager::GetProvider("openssl")`` → + ``ICryptoHandlerFactory::CreateHandler("MAC", "HMAC-SHA256")`` → ``new + MacHandler(MacExecutor, "HMAC-SHA256")``. + +3. **Context node**: ``DataManager::addChildNode`` creates a ``ContextDataNode`` + wrapping the ``MacHandlerImpl``; the ``DataNodeId`` becomes + ``context_node_id``. + +4. **Key binding**: ``KeyManagementService::BindKeyToContext(client_id, + context_node_id, key_ref_node_id, "openssl")``: + + - The ``KeyDataNode`` is located via ``DataManager::getNodeAccessor``. + - A *new* ``KeyDataNode`` is added as a child of the ``ContextDataNode``; + its constructor calls ``key_entry->AddRef(client_id)``, incrementing + ``ref_count`` to 2. + - Returns ``KeyBindingResult{key_handler_sptr, resolved_node_id}``. + +5. **Initialization**: ``MediatorImpl`` builds: + + .. code-block:: cpp + + InitializationParams params{ + .client_id = client_id, + .context_node_id = context_node_id, + .provider_id = 0, // numeric ID assigned by ProviderManager + .key_node_id = key_ref_node_id, + .bound_key_handler = key_binding_result.key_handler.get() + // non-owning raw pointer, valid during init only + }; + + Then calls ``MacHandlerImpl::InitializeContext(params)``. + +6. **Handler initialization** (``MacHandlerImpl::InitializeContext``): + + - Validates ``m_algorithm == "HMAC-SHA256"``. + - Creates ``HMAC_CTX_new()``. + - Checks ``params.bound_key_handler != nullptr``. + - Verifies ``bound_key_handler->GetProviderId() == 0`` (numeric ID; type-safety + without RTTI). + - **Downcast**: ``static_cast( + params.bound_key_handler)`` → safe because the ProviderId tag was + verified. + - Calls ``GetRawKeyBytes(key_len)`` to obtain a direct pointer to the + heap-allocated key material. + - ``HMAC_Init_ex(m_ctx, key_bytes, key_len, EVP_sha256(), nullptr)`` + initializes the HMAC context; OpenSSL copies the key material + internally. + +At the end of ``CTX_CREATE`` the daemon returns ``context_node_id`` to the +client. The key bytes have been loaded into the ``HMAC_CTX``; the +``OpenSslKeyHandler`` retains the authoritative copy until it is released. + +Step 4 — Perform MAC operations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: cpp + + mac->Update(span_of_data); + mac->Update(more_data); + auto mac_tag = mac->Finalize().value(); + +**Daemon side** — each ``Update`` becomes ``MAC_UPDATE``: + +1. ``MediatorImpl::ForwardSingleOperation`` looks up the ``ContextDataNode`` + by ``context_node_id`` → ``MacHandlerImpl::Execute(MAC_UPDATE, params)``. +2. ``MacExecutor::Execute`` validates the stream transition + (``IDLE → STREAM_INIT`` on first update; ``STREAM_INIT → STREAM_ACTIVE`` + on subsequent updates) and calls ``MacHandlerImpl::UpdateMac(dataToMac)``. +3. The handler extracts raw bytes via ``ExtractBufferData`` then calls + ``HMAC_Update(m_ctx, data, len)`` to feed data into the running HMAC. + +``MAC_FINAL``: + +1. ``MacExecutor`` validates ``STREAM_INIT/ACTIVE → IDLE`` transition. +2. ``MacHandlerImpl::FinalizeMac`` → ``HMAC_Final(m_ctx, output, &len)`` + writes the 32-byte HMAC-SHA256 tag into the client-provided + ``VirtualMemoryBuffer``. + +Step 5 — Release resources +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: cpp + + mac.reset(); // ~MacContext() → CTX_CLOSE + key_guard.reset(); // ~CryptoResourceGuard() → KEY_RELEASE + +**CTX_CLOSE** (``mac.reset()``): + ``DataManager::deleteNode(client_id, context_node_id)`` cascade-deletes: + + - ``ContextDataNode`` destroyed. + - Child ``KeyDataNode`` (bound at step 3) destroyed: + calls ``key_entry->Release(client_id)`` → ``ref_count = 1``. + - HMAC context freed via ``MacHandlerImpl::~MacHandlerImpl`` → + ``HMAC_CTX_free``. + +**KEY_RELEASE** (``key_guard.reset()``): + ``DataManager::deleteNode(client_id, key_ref_node_id)`` destroys the + original ``KeyDataNode`` (from step 2): + calls ``key_entry->Release(client_id)`` → ``ref_count = 0`` → unregister + callback → ``KeyRegistry::Unregister(registry_id)`` → registry drops + its ``shared_ptr`` → ``~KeyEntry()`` → ``IKeyHandler::Release()`` + (``OPENSSL_cleanse`` + ``delete[]``). + +The key material is now securely zeroized. + +Thread Safety +------------- + +The key management subsystem uses a three-level lock hierarchy: + +.. list-table:: Lock Hierarchy + :header-rows: 1 + :widths: 15 35 50 + + * - Level + - Lock + - Protects + * - 1 (highest) + - ``DataManager::m_mutex`` + - Node tree structure: add, delete, lookup + * - 2 + - ``KeyRegistry::m_mutex`` (per provider) + - ``m_keys`` map and ``m_slot_to_id`` slot index — independent per + provider, so OpenSSL and PKCS#11 registries never contend + * - 3 (lowest) + - ``KeyEntry::m_ref_mutex`` + - ``m_referencing_clients`` vector + +**Rule**: never acquire a lower-level lock while holding a higher-level +lock. In practice: + +- ``ReleaseKeyMaterial`` calls ``DataManager::deleteNode`` (acquires Level 1). +- The resulting ``~KeyDataNode`` calls ``key_entry->Release`` (Level 3) + *after* the ``DataManager`` lock is released. +- The unregister callback calls ``KeyRegistry::Unregister`` (Level 2) only + after ref-count reaches zero — at that point no DataManager lock is held. + +``KeyEntry::m_ref_count`` is ``std::atomic`` for lock-free +increment/decrement; the ``m_ref_mutex`` only serializes the +``m_referencing_clients`` vector updates inside ``AddRef`` / ``Release``. + +Multi-Client Key Deduplication +------------------------------- + +When multiple client processes resolve and load the **same** slot +simultaneously: + +.. code-block:: text + + App1: RESOLVE_RESOURCE("HmacSlot") → slot_node_id_A + App2: RESOLVE_RESOURCE("HmacSlot") → slot_node_id_B (independent node) + App3: RESOLVE_RESOURCE("HmacSlot") → slot_node_id_C + + App1: KEY_LOAD(slot_node_id_A) → key_ref_node_id_1 (first load → LoadKey) + App2: KEY_LOAD(slot_node_id_B) → key_ref_node_id_2 (slot loaded → reuse) + App3: KEY_LOAD(slot_node_id_C) → key_ref_node_id_3 (slot loaded → reuse) + + KeyRegistry: 1 × KeyEntry (ref_count=3) + +Each ``KeyDataNode`` is owned by the respective client's tree. +``key_entry->Release`` is called three times (once per client when the +``KeyDataNode`` destructs); only the last call triggers destruction and +zeroization. + +**Concurrent load race**: if two threads reach ``LoadOrShare`` before either +has registered, both call ``IKeySlotHandler::LoadKey``. The first call to +``KeyRegistry::RegisterSlotKey`` wins; the losing thread detects the +conflict, looks up the winning node via ``FindBySlot``, and creates a +``KeyDataNode`` on it. The losing ``IKeyHandler`` is released +immediately — no key material leaks. + +Access Control +-------------- + +.. _pkcs11_session_management: + +PKCS#11 Session Management +-------------------------- + +The PKCS#11 provider manages sessions, login state, and key object lifetime +differently from the OpenSSL provider. This section documents the design +decisions and their rationale. + +Session Pools +~~~~~~~~~~~~~ + +Each ``Pkcs11Provider`` maintains two pools of PKCS#11 sessions — one for +Read-Only (RO) and one for Read-Write (RW) operations. The pools are +protected by ``m_poolMutex`` so that the gRPC thread pool can acquire and +release sessions concurrently. + +Session acquisition: + +1. ``AcquireSession`` scans the pool for an idle session. +2. If no idle session exists and the pool is below its hard limit (from + ``C_GetTokenInfo.ulMaxSessionCount``), a new session is opened via + ``C_OpenSession``. +3. For ``kUser`` access, ``TokenAuthGuard::EnsureUserState`` is called after + the session is acquired, ensuring ``C_Login`` is called once per + module-slot pair. + +Session key pinning +~~~~~~~~~~~~~~~~~~~ + +PKCS#11 v2.40 §5.7 states that **session objects** (``CKA_TOKEN=false``) are +destroyed when the session that created them is closed. They are visible to +all sessions of the same application, but the *creating* session must remain +open. + +This means ``GenerateKey`` and ``ImportKey`` must **not** release the session +used to call ``C_GenerateKey`` / ``C_CreateObject``. The session handle is +stored alongside the key object handle in ``Pkcs11KeyStore``. + +Token objects (``CKA_TOKEN=true``) loaded via ``C_FindObjects`` do not have +this constraint — their lifetime is independent of any session. + +Thread-safe login state +~~~~~~~~~~~~~~~~~~~~~~~ + +``TokenAuthGuard`` maintains a reference-counted login state: + +- ``EnsureUserState``: if ``m_activeUserCount == 0``, calls ``C_Login``; + otherwise increments the counter. Protected by ``m_mutex``. +- ``OnUserHandlerReleased``: decrements the counter; calls ``C_Logout`` + when it reaches zero. Protected by ``m_mutex``. + +The mutex is essential because the gRPC daemon's thread pool can dispatch +concurrent crypto operations that each require a logged-in session. + +Session validation +~~~~~~~~~~~~~~~~~~ + +Before executing a cryptographic operation, the handler calls +``Pkcs11Provider::ValidateSession(session)`` which invokes +``C_GetSessionInfo``. If the session has become invalid (e.g. device +removal), the operation returns ``kSessionInvalid`` immediately instead of +propagating a cryptic PKCS#11 error code. + +Multi-Token Coexistence +~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Pkcs11ProviderFactory`` supports multiple tokens from the same +PKCS#11 library (e.g. multiple SoftHSM slots). Each ``Pkcs11TokenEntry`` +in ``Pkcs11Config`` becomes a separate ``Pkcs11Provider`` instance that +shares the ``Pkcs11Module`` (and thus ``C_Initialize`` is called once). + +The visitor pattern drives configuration: + +.. code-block:: cpp + + config.GetPkcs11Config().PopulateDefaults(); + auto factory = std::make_unique(); + config.GetPkcs11Config().Configure(*factory); // visitor call + manager.RegisterFactory(std::move(factory)); + +``Pkcs11Config::Configure()`` converts each ``Pkcs11TokenEntry`` to a +``Pkcs11ProviderConfig`` and calls ``factory.SetTokenConfigs()``; the +entire mapping logic lives in ``pkcs11_token_config.cpp`` and does not +leak into ``daemon.cpp`` or ``config.hpp``. + +Each provider has its own session pool, its own ``TokenAuthGuard``, and +its own ``Pkcs11KeyStore``. Login state, sessions, and key registrations +are fully isolated between tokens. + +Access Control +-------------- + +Access decisions are centralized in ``AccessPolicyEnforcer``. The enforcer +is called at two independent points: + +1. **Slot resolution** (``SlotRegistry::ResolveSlot``): verifies + ``client_id`` is in ``config.access_policy.allowed_uids``. +2. **Slot write operations** (generate-to-slot, import-to-slot): + ``CheckWritePermission`` verifies membership in + ``config.access_policy.allowed_write_uids``. +3. **Operation permission** (before any crypto use): + ``CheckOperationPermission`` validates the required permission bits (e.g. + ``kMac``) against ``config.allowed_operations``. +4. **Provider access** (before write): + ``CheckProviderAccess(config, provider_id, is_write)`` — writes are only + allowed via the primary provider (``provider_ids[0]``); reads may use any + listed provider. + +This "defense in depth" ensures that even if a request bypasses the +mediator's routing, the enforcer rejects unauthorized operations. + +Configuration +------------- + +Each key slot is described by a ``KeySlotConfig``: + +.. list-table:: KeySlotConfig Fields + :header-rows: 1 + :widths: 25 15 60 + + * - Field + - Type + - Description + * - ``slot_name`` + - ``string`` + - Human-readable unique name used in ``ResolveResource`` + * - ``algorithm`` + - ``string`` + - Algorithm string (e.g. ``"HMAC-SHA256"``, ``"AES-256-GCM"``) + * - ``provider_names`` + - ``vector`` + - Config-time: ordered list of human-readable provider names from JSON. Index 0 is primary. + * - ``provider_ids`` + - ``vector`` + - Runtime: ordered list of numeric IDs resolved from ``provider_names`` by ``ResolveProviderIds()``. Index 0 = primary (sole writer); others = read-only. + * - ``allowed_operations`` + - ``KeyOperationPermission`` + - Bitmask: ``kMac``, ``kEncrypt``, ``kDecrypt``, ``kSign``, … + * - ``access_policy`` + - ``AccessPolicy`` + - ``allowed_uids``, ``allowed_write_uids`` + * - ``deployment_path`` + - ``string`` + - Absolute filesystem path to the key's deployment file. + Read by ``DeploymentLoader::Load()`` at slot load time. + * - ``deployment_format`` + - ``string`` + - Serialization format token (e.g. ``"kv"``). The ``DeploymentLoader`` + façade maps this string to a concrete ``IDeploymentLoader`` implementation + (see `Deployment Descriptor`_). + +.. _Deployment Descriptor: + +Deployment Descriptor +~~~~~~~~~~~~~~~~~~~~~ + +All dynamic per-slot data that is too volatile to bake into a compiled catalog +is stored in a deployment descriptor file referenced by +``KeySlotConfig::deployment_path``. The file is read at slot load time by +``DeploymentLoader`` and written back after a key update by ``DeploymentWriter``. + +**Format-extensible design** + +The ``DeploymentLoader`` / ``DeploymentWriter`` classes are thin façades. After +validating the path they delegate to a format-specific implementation that +implements ``IDeploymentLoader`` / ``IDeploymentWriter``: + +.. code-block:: text + + slot/ + deployment_loader.hpp/.cpp ↠façade (public API unchanged for all callers) + deployment_writer.hpp/.cpp ↠façade + deployment/ + deployment_path_utils.hpp ↠IsDeploymentPathSafe() — shared guard + i_deployment_loader.hpp ↠pure-virtual interface + i_deployment_writer.hpp ↠pure-virtual interface + kv/ + kv_deployment_loader.hpp/.cpp ↠current implementation + kv_deployment_writer.hpp/.cpp + json/ ↠reserved (add JsonDeploymentLoader when needed) + flatbuffer/ ↠reserved + +To add a new format: implement ``IDeploymentLoader`` / ``IDeploymentWriter`` under +``slot/deployment//``, then add one ``if``-branch in each façade ``.cpp`` +and one dep in ``slot/deployment/BUILD``. No other files change. + +**Key=value format (``"kv"``) — file layout** + +.. code-block:: ini + + # comments are ignored; blank lines are ignored + [metadata] + availability = active + provisioned_at = 2025-11-03T08:42:00Z + update_counter = 1 + hash = sha256:a1b2c3d4... + kek.keyslotname = vehicle/master-key + kek.algo = AES-256-GCM + kek.iv = 0102030405060708090a0b0c + + [key] + key_path = /etc/crypto/keys/hmac.bin + key_format = raw + # key = + +**Well-known metadata keys** (``metadata_keys`` namespace in ``key_slot_config.hpp``): + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Key + - Meaning + * - ``availability`` + - Slot state override: ``"active"`` | ``"disabled"`` | ``"unavailable"`` + * - ``provisioned_at`` + - ISO-8601 UTC timestamp of last successful key provisioning + * - ``update_counter`` + - Monotonically increasing decimal string; incremented on every key replacement + * - ``hash`` + - Hex-encoded digest of the key material (e.g. ``"sha256:a1b2..."``) + * - ``kek.keyslotname`` + - Slot name of the Key Encryption Key used to wrap/unwrap this key + * - ``kek.algo`` + - Algorithm of the Key Encryption Key (e.g. ``"AES-256-GCM"``) + * - ``kek.iv`` + - Hex-encoded IV for KEK operations + +**Well-known deployment keys** (``deployment_keys`` namespace in ``key_slot_config.hpp``): + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Key + - Meaning + * - ``key_path`` + - Filesystem path to the key material file (file-backed providers) + * - ``key_format`` + - Encoding of the file: ``"raw"``, ``"pem"``, ``"der"`` + * - ``key`` + - Plain-text key material (hex/base64). **For testing/development only.** + * - ``pkcs11.label`` + - PKCS#11 ``CKA_LABEL`` (HSM providers) + * - ``pkcs11.object_id`` + - PKCS#11 ``CKA_ID`` as hex string + * - ``pkcs11.object_class`` + - ``"secret_key"``, ``"private_key"``, or ``"public_key"`` + * - ``tee.key_id`` + - TEE / PSA persistent key identifier + * - ``psa.key_id`` + - PSA Crypto key identifier (uint32 as decimal string) diff --git a/docs/crypto/architecture/key_management_sequence_diagrams.puml b/docs/crypto/architecture/key_management_sequence_diagrams.puml new file mode 100644 index 0000000..96a79ff --- /dev/null +++ b/docs/crypto/architecture/key_management_sequence_diagrams.puml @@ -0,0 +1,331 @@ +@startuml key_management_sequence_diagrams +!theme plain +skinparam sequenceMessageAlign center + +title Key Management — Sequence Diagrams + +' ==================================================================== +' DIAGRAM 1: DAEMON STARTUP +' ==================================================================== + +== Daemon Startup == + +participant "main()" as Main +participant "DataManager" as DM +participant "ProviderManager" as PM +participant "KeyManagementModule" as KMM +participant "KeyManagementService" as KMS +participant "BasicHandlerChainFactory" as BHCF +participant "MediatorImpl" as Med + +Main -> DM ** : make_shared() +Main -> PM ** : make_shared(config) +Main -> PM : RegisterFactory(ScoreProviderFactory) +Main -> PM : RegisterFactory(Pkcs11ProviderFactory) +Main -> PM : Initialize() + +Main -> KMM : Create(data_manager, provider_manager, key_config) +activate KMM +KMM -> KMM : m_slot_registry = make_shared() +KMM -> KMM : catalog.Load(*m_slot_registry) +note right : ConfigDrivenSlotCatalog +KMM -> KMS ** : KeyManagementService(dm, pm, slot_registry) +KMM -> PM : ForEachProvider → provider->SetKeyManagementService(service) +KMM -> KMM : slot_registry->ResolveProviderIds(provider_manager) +KMM --> Main : KeyManagementModule (Sptr) +deactivate KMM + +Main -> BHCF ** : BasicHandlerChainFactory(\n data_manager, provider_manager,\n config, module->GetService()) + +note over Main : GrpcControlServer::Start(socket) + +' ==================================================================== +' DIAGRAM 2: RESOLVE_RESOURCE — Resolve Slot Name +' ==================================================================== +newpage RESOLVE_RESOURCE — Resolve Slot Name to DataNodeId + +participant "Client" as Client +participant "MediatorImpl" as Med +participant "SlotRegistry" as KCM +participant "DataManager" as DM + +Client -> Med : HandleSingleOperation\n(OP_ACTOR_MEDIATOR, RESOLVE_RESOURCE)\nparam[0]=slot_name, param[1]=kKeySlot +activate Med + +Med -> Med : HandleMediatorOperation()\n→ HandleResourceResolutionOperation() +Med -> Med : look up resolver for kKeySlot\n(registered in RegisterResourceResolvers) + +Med -> KCM : ResolveAppResource(slot_name, client_id) +note right : ResolveSlot + AccessPolicyEnforcer::CheckSlotAccess +KCM --> Med : SlotHandle + +Med -> Med : create KeySlotDataNode\n(slot_handle, slot_registry)\n~24 bytes, no config copy +Med -> DM : addNode(client_id, slot_node) +DM --> Med : DataNodeId (slot_node_id) + +Med --> Client : response(slot_node_id) +deactivate Med + +' ==================================================================== +' DIAGRAM 3: KEY_GENERATE (Forwarded to Executor) +' ==================================================================== +newpage KEY_GENERATE — Generate Ephemeral Key + +participant "Client" as Client +participant "MediatorImpl" as Med +participant "KeyMgmtHandlerImpl\n(provider handler)" as KMH +participant "KeyManagementExecutor" as Exec +participant "IKeyFactory" as KF +participant "KeyManagementService" as KMS +participant "KeyRegistry" as KR +participant "DataManager" as DM + +Client -> Med : HandleSingleOperation\n(OP_ACTOR_KEY_MANAGEMENT, KEY_GENERATE)\nparam[0]=context_id, param[1]=client_id,\nparam[2]=context_node_id, param[3]=algorithm +activate Med + +Med -> Med : ForwardSingleOperation() +Med -> DM : getNodeAccessor(client_id, context_id)\n→ ContextDataNode → GetHandler() +DM --> Med : handler (KeyMgmtHandlerImpl) + +Med -> KMH : Execute(KEY_GENERATE, params) +activate KMH +KMH -> Exec : Execute(ctx{factory, slot_handler,\nservice, provider_id, client_id,\ncontext_node_id}, KEY_GENERATE, params) +activate Exec + +Exec -> Exec : HandleGenerate() +Exec -> KF : GenerateKey(KeyGenerationRequest{algorithm, permissions}) +note right : OpenSSL: RAND_bytes → heap buffer\nPKCS#11: C_GenerateKey (session key) +KF --> Exec : IKeyHandler::Sptr (new handler) + +Exec -> KMS : RegisterKeyMaterial(client_id, context_node_id,\nkey_handler, provider_id, slot_handle={}) +activate KMS +KMS -> KMS : GetProviderRegistry(provider_id) +KMS -> KR : RegisterEphemeralKey(key_entry) +KR --> KMS : KeyRegistryId +KMS -> DM : addChildNode(client_id, context_node_id, KeyDataNode) +note right : KeyDataNode ctor → key_entry->AddRef(client_id)\nref_count = 1 +DM --> KMS : DataNodeId (key_ref_node_id) +KMS --> Exec : KeyDataNodeResult{key_ref_node_id, key_entry} +deactivate KMS + +Exec --> KMH : ResponseParameters{key_ref_node_id} +deactivate Exec +KMH --> Med : ResponseParameters +deactivate KMH + +Med --> Client : response(key_ref_node_id) +deactivate Med + +' ==================================================================== +' DIAGRAM 4: KEY_LOAD — Load Key from Slot (with deduplication) +' ==================================================================== +newpage KEY_LOAD — Load Key from Slot (Slot Deduplication) + +participant "Client" as Client +participant "MediatorImpl" as Med +participant "KeyMgmtHandlerImpl" as KMH +participant "KeyManagementExecutor" as Exec +participant "KeyManagementService" as KMS +participant "KeyRegistry" as KR +participant "IKeySlotHandler" as SH +participant "DeploymentLoader" as DL +participant "DataManager" as DM + +Client -> Med : HandleSingleOperation\n(OP_ACTOR_KEY_MANAGEMENT, KEY_LOAD)\nparam: slot_node_id +activate Med + +Med -> Med : ForwardSingleOperation()\n→ ContextDataNode → handler +Med -> KMH : Execute(KEY_LOAD, params) +activate KMH +KMH -> Exec : Execute(ctx, KEY_LOAD, params) +activate Exec + +Exec -> Exec : HandleLoad() +Exec -> KMS : ResolveSlotForOperation(client_id, slot_node_id) +activate KMS +KMS -> DM : getNodeAccessor(slot_node_id) → KeySlotDataNode +DM --> KMS : KeySlotDataNode{slot_handle, slot_registry} +KMS --> Exec : SlotResolution{config*, slot_handle} +deactivate KMS + +Exec -> KMS : LoadOrShare(client_id, context_node_id,\nslot_handler, *config, slot_handle, provider_id) +activate KMS +KMS -> KR : FindBySlot(slot_handle) +alt Slot already loaded (another client reuses same slot) + KR --> KMS : existing KeyEntry (ref_count N) + KMS -> DM : addChildNode(client_id, context_node_id, KeyDataNode) + note right : KeyDataNode ctor → key_entry->AddRef(client_id)\nref_count = N+1 + DM --> KMS : DataNodeId (key_ref_node_id) +else First load for this slot + KR --> KMS : nullptr + KMS -> SH : LoadKey(*config) + activate SH + SH -> DL : Load(config.deployment_path,\nconfig.deployment_format) + activate DL + note right of DL : IsDeploymentPathSafe() check\nformat=="kv" → KvDeploymentLoader{}.Load(path) + DL --> SH : SlotDeploymentInfo\n{metadata, key_properties} + deactivate DL + note right of SH : FileBackedSlotHandler:\n key_properties["key_path"] → read file\n → OpenSslKeyHandler\nPkcs11KeySlotHandler:\n key_properties["pkcs11.label"/"pkcs11.object_id"]\n → C_FindObjects → Pkcs11KeyHandler + SH --> KMS : IKeyHandler::Sptr + deactivate SH + KMS -> KR : RegisterSlotKey(slot_handle, new_key_entry) + KR --> KMS : KeyRegistryId + KMS -> DM : addChildNode(client_id, context_node_id, KeyDataNode) + DM --> KMS : DataNodeId (key_ref_node_id) +end +KMS --> Exec : KeyDataNodeResult{key_ref_node_id, key_entry} +deactivate KMS + +Exec --> KMH : ResponseParameters{key_ref_node_id} +deactivate Exec +KMH --> Med : ResponseParameters +deactivate KMH +Med --> Client : response(key_ref_node_id) +deactivate Med + +' ==================================================================== +' DIAGRAM 5: CTX_CREATE with Key Binding (MAC / Cipher use-case) +' ==================================================================== +newpage CTX_CREATE with Key Binding — MAC Context Example + +participant "Client" as Client +participant "MediatorImpl" as Med +participant "ProviderManager" as PM +participant "ICryptoHandlerFactory" as Factory +participant "MacHandlerImpl" as Mac +participant "KeyManagementService" as KMS +participant "DataManager" as DM + +note over Client : Prerequisites:\n slot_node_id = RESOLVE_RESOURCE("HmacSlot")\n key_ref_node_id = KEY_LOAD(slot_node_id)\n (or KEY_GENERATE) + +Client -> Med : HandleSingleOperation\n(OP_ACTOR_MEDIATOR, CTX_CREATE)\nparam[0]="MAC", param[1]="HMAC-SHA256"\nparam[2]=CryptoProviderType::SOFTWARE\nparam[3]=key_ref_node_id +activate Med + +Med -> Med : HandleMediatorOperation()\n→ HandleContextCreationOperation() + +Med -> KMS : ResolveTargetProvider(client_id,\nSOFTWARE, key_ref_node_id) +note right : Checks key affinity:\nkey is OpenSSL → provider_id = "openssl" +KMS --> Med : resolved_provider_id = "openssl" + +Med -> PM : GetProvider("openssl") +PM --> Med : provider (IProvider*) + +Med -> Factory : CreateHandler("MAC", "HMAC-SHA256") +Factory -> Mac ** : make_shared(MacExecutor, "HMAC-SHA256") +Factory --> Med : MacHandler::Sptr + +Med -> DM : addChildNode(client_id, session_id, ContextDataNode(mac_handler)) +DM --> Med : context_node_id + +Med -> KMS : BindKeyToContext(client_id, context_node_id,\nkey_ref_node_id, "openssl") +activate KMS +note right : kKeyRef path: creates new KeyRefDataNode\nunder context_node_id\nand returns IKeyHandler::Sptr +KMS -> DM : getNodeAccessor(key_ref_node_id) → KeyRefDataNode +KMS -> DM : addChildNode(client_id, context_node_id, new KeyRefDataNode) +note right : new KeyRefDataNode ctor →\nAddRef(client_id); ref_count++ +DM --> KMS : resolved_node_id +KMS --> Med : KeyBindingResult{key_handler_sptr, resolved_node_id} +deactivate KMS + +Med -> Med : build InitializationParams{\n client_id, context_node_id,\n provider_id="openssl",\n key_node_id=key_ref_node_id,\n bound_key_handler=key_handler.get()\n} + +Med -> Mac : InitializeContext(init_params) +activate Mac +Mac -> Mac : validate algorithm (HMAC-SHA256 ✓) +Mac -> Mac : HMAC_CTX_new() +Mac -> Mac : downcast bound_key_handler\n→ OpenSslKeyHandler\n(type-safe: check ProviderId == "openssl") +Mac -> Mac : GetRawKeyBytes(key_len)\n→ key_bytes pointer +Mac -> Mac : store key bytes reference\n(valid while KeyDataNode alive) +Mac --> Med : Expected OK +deactivate Mac + +Med --> Client : response(context_node_id) +deactivate Med + +' ==================================================================== +' DIAGRAM 6: MAC Operation Streaming (UPDATE → FINAL) +' ==================================================================== +newpage MAC Streaming — UPDATE, FINAL, CTX_CLOSE + +participant "Client" as Client +participant "MediatorImpl" as Med +participant "MacHandlerImpl" as Mac +participant "MacExecutor" as Exec +participant "DataManager" as DM + +note over Client : context_node_id established in CTX_CREATE with Key Binding + +Client -> Med : HandleSingleOperation\n(OP_ACTOR_MAC, MAC_UPDATE)\nparam[context_id=context_node_id], param[data_buf] +activate Med +Med -> Med : ForwardSingleOperation()\n→ getNodeAccessor(context_node_id)\n→ ContextDataNode → handler +Med -> Mac : Execute(MAC_UPDATE, params) +activate Mac +Mac -> Exec : Execute(handler=*this, MAC_UPDATE, params) +note right : ValidateStreamTransition:\nIDLE → STREAM_INIT on first MAC_UPDATE +Exec -> Mac : UpdateMac(data_ptr, data_len) +Mac -> Mac : HMAC_Update(m_ctx, data, len) +Mac --> Exec : OK +Exec --> Mac : ResponseParameters{} +Mac --> Med : ResponseParameters{} +deactivate Mac +Med --> Client : response(OK) +deactivate Med + +Client -> Med : HandleSingleOperation\n(OP_ACTOR_MAC, MAC_FINAL)\nparam[context_id], param[output_buf] +activate Med +Med -> Mac : Execute(MAC_FINAL, params) +activate Mac +Mac -> Exec : Execute(handler=*this, MAC_FINAL, params) +note right : ValidateStreamTransition:\nSTREAM_INIT/ACTIVE → IDLE +Exec -> Mac : FinalizeMac(output_buf) +Mac -> Mac : HMAC_Final(m_ctx, output, &len) +Mac -> Mac : write MAC bytes to VirtualMemoryBuffer +Mac --> Exec : ResponseParameters{mac_bytes} +Exec --> Mac : ResponseParameters +Mac --> Med : ResponseParameters +deactivate Mac +Med --> Client : response(mac_bytes) +deactivate Med + +Client -> Med : HandleSingleOperation\n(OP_ACTOR_MEDIATOR, CTX_CLOSE)\ncontext_id +activate Med +Med -> Med : HandleContextCloseOperation() +Med -> DM : deleteNode(client_id, context_node_id) +note right : Cascade deletion:\n ContextDataNode deleted\n → child KeyDataNode deleted\n → ~KeyDataNode()\n → KeyEntry::Release(client_id)\n → if last ref: registry.Unregister()\n → ~KeyEntry()\n → handler->Release() (zeroize) +DM --> Med : OK +Med --> Client : response(OK) +deactivate Med + +' ==================================================================== +' DIAGRAM 7: CRASH CLEANUP — Client Process Death +' ==================================================================== +newpage Crash Cleanup — Client Process Death + +participant "Daemon\n(connection monitor)" as Daemon +participant "DataManager" as DM +participant "KeyManagementService" as KMS +participant "KeyRegistry" as KR +participant "KeyDataNode" as KDN + +Daemon -> DM : deleteClientNodes(client_id) +note right : Triggered when gRPC stream closes\nor process death detected +activate DM +DM -> DM : post-order traversal of client's subtree +DM -> DM : delete ContextDataNode\n→ child KeyDataNodes cascade-deleted +note right : Each ~KeyDataNode():\n key_entry->Release(client_id): ref_count--\n if last ref: on_last_release → Unregister +DM --> Daemon : OK +deactivate DM + +Daemon -> KMS : CleanupClient(client_id) +note right : Safety net: catches any refs\nnot cleaned via tree deletion +activate KMS +KMS -> KR : CleanupClient(client_id) [for each registry] +activate KR +KR -> KDN : Release(client_id) [for each key] +note right : if ref_count → 0:\n erase from m_keys + m_slot_to_id +KR --> KMS : done +deactivate KR +KMS --> Daemon : done +deactivate KMS + +@enduml diff --git a/docs/crypto/architecture/provider_architecture.puml b/docs/crypto/architecture/provider_architecture.puml new file mode 100644 index 0000000..4fc3b2c --- /dev/null +++ b/docs/crypto/architecture/provider_architecture.puml @@ -0,0 +1,199 @@ +@startuml provider_architecture +!theme plain +skinparam linetype ortho +skinparam classAttributeIconSize 0 +skinparam classFontSize 11 +skinparam packageFontSize 12 + +title Daemon Provider Architecture + +' ==================================================================== +' CONFIGURATION LAYER +' ==================================================================== +package "Configuration (config/)" <> #FFF8DC { + class Config { + +GetScoreProviderConfig() : ScoreProviderConfig& + +GetPkcs11Config() : Pkcs11Config& + } + + class ScoreProviderConfig { + +AddProviderEntry(entry) + +PopulateDefaults() + +Configure(factory : ScoreProviderFactory&) + } + + class Pkcs11Config { + +AddTokenEntry(entry) + +PopulateDefaults() + +Configure(factory : Pkcs11ProviderFactory&) + } + + Config *-- ScoreProviderConfig + Config *-- Pkcs11Config +} + +' ==================================================================== +' PROVIDER MANAGER +' ==================================================================== +package "Provider Infrastructure (provider/)" <> #F5F5F5 { + + interface IProviderFactory { + +CreateAndRegister(manager : ProviderManager&) : bool + } + + class ProviderManager { + +RegisterFactory(factory : unique_ptr) + +Initialize() : bool + +RegisterProvider(name, provider, type) : bool + +GetProvider(id) : IProvider::Sptr + } + + interface IProvider { + +Initialize() : Result + +Shutdown() : Result + +CreateHandlerFactory() : unique_ptr + } + + ProviderManager *-- IProviderFactory : 0..n + ProviderManager *-- IProvider : 1..n + IProviderFactory ..> ProviderManager : registers into + + ' ==================================================================== + ' HANDLER SHARED INFRASTRUCTURE + ' ==================================================================== + package "Handler Base (handler/)" <> #EEEEEE { + + interface IHandler { + +Execute(request, context) : Result + +GetInitParams() : HandlerInitParams + } + + interface ICryptoHandlerFactory { + +CreateHandler(config) : unique_ptr + } + + package "operations/" <> #E8E8E8 { + note "hash_handler_operations.hpp\nmac_handler_operations.hpp\n\nShared OperationAction constants.\nUsed identically by all providers." as opsNote + } + } + + ' ==================================================================== + ' SCORE PROVIDER FAMILY + ' ==================================================================== + package "Score Provider Family (score_provider/)" <> #E8F5E9 { + + class ScoreProviderFactory { + -m_configs : vector + +SetConfigs(configs) + +CreateAndRegister(manager) : bool + } + ScoreProviderFactory .up.|> IProviderFactory + ScoreProviderConfig ..> ScoreProviderFactory : Configure() calls SetConfigs() + + class OpenSSLProviderFactory { + +CreateAndRegister(manager) : bool + } + ScoreProviderFactory ..> OpenSSLProviderFactory : delegates to + + abstract class ScoreProvider { + +Initialize() : Result + +Shutdown() : Result + #CreateHandlerFactory() : unique_ptr + } + ScoreProvider .up.|> IProvider + + class OpenSSL { + +Initialize() : Result + +Shutdown() : Result + } + OpenSSL --|> ScoreProvider + OpenSSLProviderFactory ..> OpenSSL : creates + + package "operations/ (base handlers + executors)" <> #D5F0D5 { + + abstract class ScoreHandlerFactory { + +CreateHashHandler(...) : unique_ptr + +CreateMacHandler(...) : unique_ptr + +CreateKeyManagementHandler(...) : unique_ptr + } + ScoreHandlerFactory .up.|> ICryptoHandlerFactory + + abstract class ScoreHashHandler { + #StartHash(...) : Result + #UpdateHash(...) : Result + #FinishHash(...) : Result + -m_executor : HashExecutor + } + ScoreHashHandler .up.|> IHandler + + abstract class ScoreMacHandler { + #StartMac(...) : Result + #UpdateMac(...) : Result + #FinishMac(...) : Result + -m_executor : MacExecutor + } + ScoreMacHandler .up.|> IHandler + + abstract class ScoreKeyManagementHandler { + -m_executor : KeyManagementExecutor + } + ScoreKeyManagementHandler .up.|> IHandler + + class HashExecutor <> + class MacExecutor <> + + ScoreHashHandler *-- HashExecutor + ScoreMacHandler *-- MacExecutor + } + + package "openssl/ (concrete handlers)" <> #C8E6C9 { + + class OpenSslHandlerFactory + OpenSslHandlerFactory --|> ScoreHandlerFactory + + class OpenSslHashHandler + OpenSslHashHandler --|> ScoreHashHandler + + class OpenSslHmacHandler + OpenSslHmacHandler --|> ScoreMacHandler + + class OpenSslKeyManagementHandler + OpenSslKeyManagementHandler --|> ScoreKeyManagementHandler + + OpenSslHandlerFactory ..> OpenSslHashHandler : creates + OpenSslHandlerFactory ..> OpenSslHmacHandler : creates + OpenSslHandlerFactory ..> OpenSslKeyManagementHandler : creates + } + + OpenSSL ..> OpenSslHandlerFactory : uses + } + + ' ==================================================================== + ' PKCS#11 PROVIDER FAMILY + ' ==================================================================== + package "PKCS#11 Provider Family (pkcs11/)" <> #E3F2FD { + + class Pkcs11ProviderFactory { + -m_configs : vector + +SetTokenConfigs(configs) + +CreateAndRegister(manager) : bool + } + Pkcs11ProviderFactory .up.|> IProviderFactory + Pkcs11Config ..> Pkcs11ProviderFactory : Configure() calls SetTokenConfigs() + + class Pkcs11Provider + Pkcs11Provider .up.|> IProvider + Pkcs11ProviderFactory ..> Pkcs11Provider : creates + + note bottom of Pkcs11ProviderFactory + Each token entry produces + one Pkcs11Provider instance. + end note + } + + ' shared ops used by both families + OpenSslHashHandler ..> opsNote : includes + OpenSslHmacHandler ..> opsNote : includes +} + +@enduml diff --git a/docs/crypto/architecture/provider_architecture.rst b/docs/crypto/architecture/provider_architecture.rst new file mode 100644 index 0000000..78b13bc --- /dev/null +++ b/docs/crypto/architecture/provider_architecture.rst @@ -0,0 +1,107 @@ +.. + # ============================================================================= + # C O P Y R I G H T + # ----------------------------------------------------------------------------- + # Copyright (c) 2026 by ETAS GmbH. All rights reserved. + # + # The reproduction, distribution and utilization of this file as + # well as the communication of its contents to others without express + # authorization is prohibited. Offenders will be held liable for the + # payment of damages. All rights reserved in the event of the grant + # of a patent, utility model or design. + # ============================================================================= + +.. _crypto_provider_architecture: + +Provider Architecture +===================== + +The daemon hosts two parallel provider families — **Score** (software-oriented, +currently backed by OpenSSL) and **PKCS#11** (hardware token / SoftHSM). Both +families implement the same ``IProvider`` / ``IProviderFactory`` interfaces and +are registered with ``ProviderManager`` through an identical visitor-pattern +bootstrapping sequence. + +.. uml:: provider_architecture.puml + :align: center + :caption: Daemon Provider Architecture — Score and PKCS#11 families, handler hierarchy, and config visitor pattern. + :alt: UML class diagram of the provider architecture. + +Provider Families +----------------- + +Score Provider Family +~~~~~~~~~~~~~~~~~~~~~ + +The score family (``provider/score_provider/``) provides typed abstract handler +bases that sit between the generic ``IHandler`` interface and concrete +implementations: + +- ``ScoreHashHandler`` owns a ``HashExecutor`` that drives the stream state + machine (``HASH_INIT`` → ``HASH_UPDATE`` → ``HASH_FINISH``). +- ``ScoreMacHandler`` owns a ``MacExecutor`` with the equivalent MAC state machine. +- ``ScoreKeyManagementHandler`` delegates to a shared ``KeyManagementExecutor`` + (from ``provider/executors/``). + +Concrete providers (e.g. ``openssl/``) inherit from these bases and override +only the typed crypto primitive methods (``StartHash``, ``UpdateHash``, +``FinishHash``). They do not re-implement the state machine logic. + +PKCS#11 Provider Family +~~~~~~~~~~~~~~~~~~~~~~~ + +The PKCS#11 family (``provider/pkcs11/``) implements ``IHandler`` directly. +Each handler translates generic operation requests into PKCS#11 C API calls +against a token. Shared key management logic is provided via the same +``KeyManagementExecutor`` used by the score family. + +Shared Operation Constants +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``handler/operations/hash_handler_operations.hpp`` and +``handler/operations/mac_handler_operations.hpp`` define the ``OperationAction`` +integer constants (``HASH_INIT``, ``HASH_UPDATE``, ``HASH_FINISH``, +``MAC_INIT``, etc.) that identify each IPC operation. +Both provider families include these headers directly — the constants are not +specific to any algorithm family or provider. + + +Directory Layout +---------------- + +.. code-block:: text + + provider/ + ├── handler/ + │ ├── i_handler.hpp ↠Handler interface + │ ├── i_crypto_handler_factory.hpp ↠Factory interface + │ ├── handler_init_params.hpp + │ ├── context_data_node.hpp + │ ├── operations/ + │ │ ├── hash_handler_operations.hpp ↠Shared hash OperationAction constants + │ │ └── mac_handler_operations.hpp ↠Shared MAC OperationAction constants + │ └── src/ + │ ├── handler_utils.hpp/.cpp + │ └── context_data_node.cpp + ├── executors/ + │ ├── key_mgmt_executor.hpp/.cpp ↠Shared KM executor (both families) + │ ├── key_mgmt_context.hpp + │ └── key_mgmt_request_parser.hpp + ├── score_provider/ + │ ├── score_provider_config.hpp/.cpp ↠Config / visitor + │ ├── score_provider_factory.hpp/.cpp + │ ├── score_provider.hpp/.cpp ↠Abstract base provider + │ ├── operations/ + │ │ ├── hash/ ↠ScoreHashHandler + HashExecutor + │ │ ├── mac/ ↠ScoreMacHandler + MacExecutor + │ │ ├── key_management/ ↠ScoreKeyManagementHandler + │ │ └── factory/ ↠ScoreHandlerFactory + │ └── openssl/ ↠OpenSSL concrete provider + │ ├── provider_openssl.hpp/.cpp + │ ├── openssl_provider_factory.hpp/.cpp + │ ├── operations/ ↠OpenSsl*Handler implementations + │ ├── key_management/ ↠OpenSslKeyHandler, OpenSslKeyFactory + │ └── detail/ + ├── pkcs11/ ↠PKCS#11 provider family + └── src/ + └── provider_manager.cpp diff --git a/docs/crypto/architecture/typical_usage_sequence.puml b/docs/crypto/architecture/typical_usage_sequence.puml new file mode 100644 index 0000000..cad044f --- /dev/null +++ b/docs/crypto/architecture/typical_usage_sequence.puml @@ -0,0 +1,94 @@ +@startuml typical_usage_sequence + +'TODO: Update the diagram to reflect the latest API design and interaction patterns, including any new features or changes in the flow. + +title Score Crypto API — Typical Usage Flow + +skinparam sequenceMessageAlign center +skinparam responseMessageBelowArrow true + +participant "Application" as App +participant "score::mw::crypto\n(Client Library)" as Lib +participant "Crypto Daemon" as Daemon + +== Stack Initialization == + +App -> Lib : CreateCryptoStack(CryptoStackConfig) +activate Lib +Lib -> Daemon : connect(connection_endpoint) +activate Daemon +Daemon --> Lib : connection established +Lib --> App : Result +deactivate Lib + +== Memory Allocation (Data Plane) == + +App -> Lib : ICryptoStack::GetMemoryAllocator() +activate Lib +Lib --> App : Result +deactivate Lib + +App -> Lib : IMemoryAllocator::Allocate(size) +activate Lib +Lib -> Daemon : allocate shared memory +Daemon --> Lib : IReadWriteMemoryRegion +Lib --> App : Result +deactivate Lib + +App -> App : write data to region + +== Context & Resource Resolution == + +App -> Lib : ICryptoStack::CreateCryptoContext() +activate Lib +Lib -> Daemon : create session +Daemon --> Lib : session handle +Lib --> App : Result +deactivate Lib + +App -> Lib : ICryptoContext::ResolveResource(\n ResourceId, ResourceType::kKeySlot) +activate Lib +Lib -> Daemon : resolve + ACL check (uid) +Daemon --> Lib : resource handle +Lib --> App : Result +deactivate Lib + +== Hash Operation (Streaming) == + +App -> Lib : ICryptoContext::CreateHashContext(\n HashContextConfig) +activate Lib +Lib -> Daemon : create hash context +Daemon --> Lib : context handle +Lib --> App : Result +deactivate Lib + +App -> Lib : IHashContext::Init() +activate Lib +Lib -> Daemon : forward to provider +Daemon --> Lib : Result +Lib --> App : Result +deactivate Lib + +App -> Lib : IHashContext::Update(span) +activate Lib +Lib -> Daemon : forward to provider +Daemon --> Lib : Result +Lib --> App : Result +deactivate Lib + +App -> Lib : IHashContext::Finalize(span) +activate Lib +Lib -> Daemon : forward to provider +Daemon --> Lib : digest bytes +Lib --> App : Result +deactivate Lib + +== Cleanup == + +App -> Lib : ~ICryptoStack() +activate Lib +Lib -> Daemon : disconnect +deactivate Daemon +deactivate Lib + +@enduml diff --git a/docs/index.rst b/docs/index.rst index f8e53da..eba9b45 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,21 +1,25 @@ .. - # ******************************************************************************* - # Copyright (c) 2024 Contributors to the Eclipse Foundation - # - # See the NOTICE file(s) distributed with this work for additional - # information regarding copyright ownership. - # - # This program and the accompanying materials are made available under the - # terms of the Apache License Version 2.0 which is available at - # https://www.apache.org/licenses/LICENSE-2.0 - # - # SPDX-License-Identifier: Apache-2.0 - # ******************************************************************************* - -Module Template Documentation -============================= - -This documentation describes the structure, usage and configuration of the Bazel-based C++/Rust module template. + # ============================================================================= + # C O P Y R I G H T + # ----------------------------------------------------------------------------- + # Copyright (c) 2026 by ETAS GmbH. All rights reserved. + # + # The reproduction, distribution and utilization of this file as + # well as the communication of its contents to others without express + # authorization is prohibited. Offenders will be held liable for the + # payment of damages. All rights reserved in the event of the grant + # of a patent, utility model or design. + # ============================================================================= + +Crypto Documentation +==================== + +This documentation covers the **Crypto** module — a cryptographic +middleware stack for automotive ECUs. The module +provides a C++ client library (``score::mw::crypto``) and an accompanying +crypto daemon that together deliver key management, symmetric and asymmetric +cryptography, hashing, signing, certificate handling, and secure memory +allocation. .. contents:: Table of Contents :depth: 2 @@ -24,19 +28,39 @@ This documentation describes the structure, usage and configuration of the Bazel Overview -------- -This repository provides a standardized setup for projects using **C++** or **Rust** and **Bazel** as a build system. -It integrates best practices for build, test, CI/CD and documentation. +The ``score::mw::crypto`` module follows a **daemon-based client–server** +architecture: + +- **Client library** (``//score/mw/crypto/api/...``) — a pure C++ interface layer + that applications link against. All operations are expressed through + factory-created context objects (``IHashContext``, ``IMacContext``, etc.) + following an ``Init()`` → ``Update()`` → ``Finalize()`` streaming pattern. + +- **Crypto daemon** (``//score/crypto/daemon:crypto_daemon``) — a separate process that + hosts all cryptographic state, enforces per-application Access Control + List (ACL) and per-key operation permissions (``KeyOperationPermission`` + bitmask), and drives the underlying provider (OpenSSL, SoftHSM / PKCS#11, + or future HSM/TEE backends). + +- **IPC transport** — gRPC over a Unix domain socket (Temporary solution). + ABI compatibility between library and daemon shall be guarenteed. Requirements ------------ +.. + TODO: write comp_requirements. -.. stkh_req:: Example Functional Requirement - :id: stkh_req__docgen_enabled__example - :status: valid - :safety: QM - :security: YES - :reqtype: Functional - :rationale: Ensure documentation builds are possible for all modules +Functional requirements are captured implicitly via the public interface +specifications in :ref:`crypto_interfaces` and the design decisions in +:ref:`crypto_design_decisions`. + +Architecture +------------ + +.. toctree:: + :maxdepth: 2 + + crypto/architecture/index.rst Project Layout @@ -44,39 +68,30 @@ Project Layout The module template includes the following top-level structure: -- `src/`: Main C++/Rust sources -- `tests/`: Unit and integration tests -- `examples/`: Usage examples +- `.github/workflows/`: CI pipelines for Bazel build, unit tests, and documentation generation - `docs/`: Documentation using `docs-as-code` -- `.github/workflows/`: CI/CD pipelines +- `examples/`: Usage examples for each context type (hash, encrypt, sign, key management, certificates) +- `tests/`: Unit tests, integration tests, provider tests, and test vectors +- `third_party/`: Patches for third-party dependencies (if any) Quick Start ----------- -To build the module: +To build documentation: .. code-block:: bash - bazel build //src/... + bazel run //:docs -To run tests: +To build the module: .. code-block:: bash - bazel test //tests/... - -Configuration -------------- - -The `project_config.bzl` file defines metadata used by Bazel macros. + bazel build //score/... //tests/... -Example: - -.. code-block:: python +To run tests: - PROJECT_CONFIG = { - "asil_level": "QM", - "source_code": ["cpp", "rust"] - } +.. code-block:: bash -This enables conditional behavior (e.g., choosing `clang-tidy` for C++ or `clippy` for Rust). + # Execute tests + bazel test //tests/... diff --git a/examples/BUILD b/examples/BUILD index 012dd54..9513f07 100644 --- a/examples/BUILD +++ b/examples/BUILD @@ -1,8 +1,44 @@ -# Needed for Dash tool to check python dependency licenses. -filegroup( - name = "cargo_lock", - srcs = [ - "Cargo.lock", +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_binary") + +cc_binary( + name = "hashing_example", + srcs = ["hashing_example.cpp"], + deps = [ + "//score/mw/crypto/api:crypto_stack", + "//score/mw/crypto/api/config:context_configs", + "//score/mw/crypto/api/contexts:crypto_contexts", ], - visibility = ["//visibility:public"], ) + +cc_binary( + name = "memory_allocator_example", + srcs = ["memory_allocator_example.cpp"], + deps = [ + "//score/mw/crypto/api:crypto_stack", + "//score/mw/crypto/api/common:crypto_common", + "//score/mw/crypto/api/config:context_configs", + "//score/mw/crypto/api/contexts:crypto_contexts", + ], +) + +# ============================================================================= +# FUTURE: Examples below depend on APIs not yet in scope. +# ============================================================================= +# cc_binary(name = "encryption_example", srcs = ["encryption_example.cpp"], ...) +# cc_binary(name = "pqc_signing_example", srcs = ["pqc_signing_example.cpp"], ...) +# cc_binary(name = "certificate_example", srcs = ["certificate_example.cpp"], ...) +# cc_binary(name = "ephemeral_key_example",srcs = ["ephemeral_key_example.cpp"],...) # needs cipher context +# cc_binary(name = "cross_provider_example",srcs = ["cross_provider_example.cpp"],...) +# cc_binary(name = "key_lifecycle_complete",srcs = ["key_lifecycle_complete.cpp"],..) diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..4cb8a45 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,5 @@ + +# Remark on examples + +The example source files are only kept for reference. +For an actual executable or sample application refer to the tests/integration_tests folder. diff --git a/examples/hashing_example.cpp b/examples/hashing_example.cpp new file mode 100644 index 0000000..a905381 --- /dev/null +++ b/examples/hashing_example.cpp @@ -0,0 +1,178 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +/// @file hashing_example.cpp +/// @brief Demonstrates SHA-256 hashing using the score::mw::crypto API. +/// +/// Shows both streaming (Init → Update* → Finalize) and single-shot modes. + +#include "score/mw/crypto/api/config/hash_context_config.hpp" +#include "score/mw/crypto/api/contexts/i_hash_context.hpp" +#include "score/mw/crypto/api/crypto_stack_factory.hpp" +#include "score/mw/crypto/api/i_crypto_context.hpp" +#include "score/mw/crypto/api/i_crypto_stack.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using namespace score::mw::crypto; + +namespace +{ + +void PrintHex(const char* label, const uint8_t* data, std::size_t len) +{ + std::printf("%s: ", label); + for (std::size_t i = 0; i < len; ++i) + { + std::printf("%02x", data[i]); + } + std::printf("\n"); +} + +} // namespace + +int main() +{ + // 1. Create the crypto stack and connect to the daemon + CryptoStackConfig stack_config; + stack_config.SetConnectionEndpoint("unix:///var/run/crypto-daemon.sock"); + + auto stack_result = CreateCryptoStack(stack_config); + if (!stack_result.has_value()) + { + std::cout << "Error: Failed to create crypto stack" << std::endl; + return 1; + } + auto stack = std::move(stack_result).value(); + + // 2. Create a crypto context (session) + auto ctx_result = stack->CreateCryptoContext(); + if (!ctx_result.has_value()) + { + std::cout << "Error: Failed to create crypto context" << std::endl; + return 1; + } + auto ctx = std::move(ctx_result).value(); + + // 3. Configure and create a SHA-256 hash context + HashContextConfig hash_config; + hash_config.SetAlgorithm("SHA-256"); + + auto hash_result = ctx->CreateHashContext(hash_config); + if (!hash_result.has_value()) + { + std::cout << "Error: Failed to create hash context" << std::endl; + return 1; + } + auto hash = std::move(hash_result).value(); + + // 4. Streaming hash: Init → Update → Update → Finalize + constexpr std::size_t kSha256DigestSize = 32; + std::array digest{}; + + const char* chunk1 = "Hello, "; + const char* chunk2 = "World!"; + + auto init_result = hash->Init(); + if (!init_result.has_value()) + { + std::cout << "Error: Init failed" << std::endl; + return 1; + } + + hash->Update({reinterpret_cast(chunk1), std::strlen(chunk1)}); + hash->Update({reinterpret_cast(chunk2), std::strlen(chunk2)}); + + auto finalize_result = hash->Finalize({digest.data(), digest.size()}); + if (!finalize_result.has_value()) + { + std::cout << "Error: Finalize failed" << std::endl; + return 1; + } + + PrintHex("Streaming SHA-256", digest.data(), finalize_result.value()); + + // 5. Single-shot hash (equivalent to Init + Update + Finalize) + const char* message = "Hello, World!"; + std::array digest2{}; + + auto single_result = hash->SingleShot({reinterpret_cast(message), std::strlen(message)}, + {digest2.data(), digest2.size()}); + + if (!single_result.has_value()) + { + std::cout << "Error: SingleShot failed" << std::endl; + return 1; + } + + PrintHex("SingleShot SHA-256", digest2.data(), single_result.value()); + + // 6. Verify both produce the same digest + if (digest == digest2) + { + std::cout << "OK: Streaming and single-shot digests match" << std::endl; + } + + // 7. Context reuse via Reset() + // Reset() returns the context to its post-construction state — the key + // (none for hash) and algorithm binding are preserved but the streaming + // state machine and intermediate data are cleared. This avoids the + // factory + IPC cost of creating a new context, which matters for + // high-throughput scenarios (per-frame V2X AEAD, bulk log hashing). + auto reset_result = hash->Reset(); + if (!reset_result.has_value()) + { + std::cout << "Error: Reset failed" << std::endl; + return 1; + } + + // Hash a different message using the same context + const char* second_msg = "Context reuse is efficient!"; + std::array digest3{}; + + hash->Init(); + hash->Update({reinterpret_cast(second_msg), std::strlen(second_msg)}); + auto finalize3 = hash->Finalize({digest3.data(), digest3.size()}); + if (!finalize3.has_value()) + { + std::cout << "Error: Finalize after Reset failed" << std::endl; + return 1; + } + PrintHex("Reused-ctx SHA-256", digest3.data(), finalize3.value()); + + // Reset() also works mid-stream to abort and restart + hash->Init(); + hash->Update({reinterpret_cast(chunk1), std::strlen(chunk1)}); + hash->Reset(); // discard partial work + + hash->Init(); + hash->Update({reinterpret_cast(second_msg), std::strlen(second_msg)}); + std::array digest4{}; + hash->Finalize({digest4.data(), digest4.size()}); + + if (digest3 == digest4) + { + std::cout << "OK: Reset mid-stream + re-hash matches" << std::endl; + } + + // 8. Query digest size + auto digest_size = hash->GetDigestSize(); + std::cout << "Digest size: " << digest_size << " bytes" << std::endl; + + return 0; +} diff --git a/examples/memory_allocator_example.cpp b/examples/memory_allocator_example.cpp new file mode 100644 index 0000000..63caba1 --- /dev/null +++ b/examples/memory_allocator_example.cpp @@ -0,0 +1,144 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +/// @file memory_allocator_example.cpp +/// @brief Demonstrates the data-plane shared memory allocator and zero-copy path. +/// +/// Shows default and provider-compatible memory allocation, quota management, +/// and how shared memory regions are used with operation contexts. + +#include "score/mw/crypto/api/common/i_memory_allocator.hpp" +#include "score/mw/crypto/api/common/i_memory_region.hpp" +#include "score/mw/crypto/api/config/hash_context_config.hpp" +#include "score/mw/crypto/api/contexts/i_hash_context.hpp" +#include "score/mw/crypto/api/crypto_stack_factory.hpp" +#include "score/mw/crypto/api/i_crypto_context.hpp" +#include "score/mw/crypto/api/i_crypto_stack.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace score::mw::crypto; + +int main() +{ + // 1. Create crypto stack + CryptoStackConfig stack_config; + stack_config.SetConnectionEndpoint("unix:///var/run/crypto-daemon.sock"); + + auto stack = CreateCryptoStack(stack_config).value(); + + // ────────────────────────────────────────────────────────────────────── + // 2. Access the memory allocator (data plane, lifetime tied to stack) + // ────────────────────────────────────────────────────────────────────── + auto allocator_result = stack->GetMemoryAllocator(); + if (!allocator_result) + { + std::cerr << "Failed to access memory allocator: " << allocator_result.error().Message().data() << std::endl; + return 1; + } + auto allocator = std::move(allocator_result).value(); + + std::cout << "Memory allocator:" << std::endl; + std::cout << " Quota: " << allocator->GetQuota() << " bytes" << std::endl; + std::cout << " Current usage: " << allocator->GetCurrentUsage() << " bytes" << std::endl; + + // ────────────────────────────────────────────────────────────────────── + // 3. Allocate default shared memory + // ────────────────────────────────────────────────────────────────────── + constexpr std::size_t kDataSize = 1024; + auto region = allocator->Allocate(kDataSize).value(); + + std::cout << std::endl << "Allocated default region:" << std::endl; + std::cout << " Size: " << region->size() << " bytes" << std::endl; + std::cout << " Usage after alloc: " << allocator->GetCurrentUsage() << " bytes" << std::endl; + + // Write data into the shared memory region + const char* message = "Data to hash via shared memory"; + const auto msg_len = std::strlen(message); + auto writable = region->AsWritableSpan(); + std::memcpy(writable.data(), message, msg_len); + + std::cout << " Wrote " << msg_len << " bytes into shared memory" << std::endl; + + // ────────────────────────────────────────────────────────────────────── + // 4. Use the shared memory region with a hash context (zero-copy path) + // ────────────────────────────────────────────────────────────────────── + auto ctx = stack->CreateCryptoContext().value(); + + HashContextConfig hash_config; + hash_config.SetAlgorithm("SHA-256"); + auto hash = ctx->CreateHashContext(hash_config).value(); + + // Pass shared memory span directly to the hash context + // When using default memory, the daemon copies internally. + // When using provider-compatible memory, this is zero-copy. + std::array digest{}; + + auto bytes = hash->SingleShot(region->AsSpan().subspan(0, msg_len), // read-only view of the region + {digest.data(), digest.size()}) + .value(); + + std::cout << std::endl << "SHA-256 digest: "; + for (std::size_t i = 0; i < bytes; ++i) + { + std::printf("%02x", digest[i]); + } + std::printf("\n"); + + // ────────────────────────────────────────────────────────────────────── + // 5. Provider-compatible allocation for true zero-copy + // ────────────────────────────────────────────────────────────────────── + // Resolve a provider handle + auto provider = ctx->ResolveResource("HW_Provider", ResourceType::kProvider).value(); + + // Allocate memory compatible with the specific provider (e.g., DMA-capable) + auto hw_region = allocator->Allocate(kDataSize, MemoryType::kProviderCompatible, provider).value(); + + std::cout << std::endl << "Allocated provider-compatible region:" << std::endl; + std::cout << " Size: " << hw_region->size() << " bytes" << std::endl; + std::cout << " Usage after alloc: " << allocator->GetCurrentUsage() << " bytes" << std::endl; + + // Write data and use with a context — this path is zero-copy end-to-end: + // Application → shared memory → daemon → provider device + // No copies at any boundary. + auto hw_writable = hw_region->AsWritableSpan(); + std::memcpy(hw_writable.data(), message, msg_len); + + // ────────────────────────────────────────────────────────────────────── + // 6. Region resizing + // ────────────────────────────────────────────────────────────────────── + auto resize_result = hw_region->Resize(2048); + if (resize_result.has_value()) + { + std::cout << std::endl << "Resized region to " << hw_region->size() << " bytes" << std::endl; + // NOTE: Previously obtained pointers/spans may be invalidated! + } + + // ────────────────────────────────────────────────────────────────────── + // 7. Memory region destruction and quota tracking + // ────────────────────────────────────────────────────────────────────── + std::cout << std::endl + << "Usage before region destruction: " << allocator->GetCurrentUsage() << " bytes" << std::endl; + + region.reset(); // Release default region + hw_region.reset(); // Release provider-compatible region + + std::cout << "Usage after region destruction: " << allocator->GetCurrentUsage() << " bytes" << std::endl; + + // Stack destruction releases any remaining regions automatically. + return 0; +} diff --git a/platforms/BUILD b/platforms/BUILD new file mode 100644 index 0000000..ecdd164 --- /dev/null +++ b/platforms/BUILD @@ -0,0 +1,49 @@ +# Custom platform definitions for QNX builds +# This wraps the score_bazel_platforms platform and adds the standard @platforms//os:qnx +# constraint so that select() statements in external dependencies (grpc, abseil) can match it. + +platform( + name = "x86_64-qnx-extended", + constraint_values = [ + "@platforms//os:qnx", # Add standard QNX OS constraint for select() matching + ], + parents = ["@score_bazel_platforms//:x86_64-qnx-sdp_8.0.0-posix"], + visibility = ["//visibility:public"], +) + +platform( + name = "aarch64-qnx-extended", + constraint_values = [ + "@platforms//os:qnx", # Add standard QNX OS constraint for select() matching + ], + parents = ["@score_bazel_platforms//:aarch64-qnx-sdp_8.0.0-posix"], + visibility = ["//visibility:public"], +) + +# Config settings for selecting on OS + CPU combinations +config_setting( + name = "is_qnx_aarch64", + constraint_values = [ + "@platforms//os:qnx", + "@platforms//cpu:aarch64", + ], + visibility = ["//visibility:public"], +) + +config_setting( + name = "is_qnx_x86_64", + constraint_values = [ + "@platforms//os:qnx", + "@platforms//cpu:x86_64", + ], + visibility = ["//visibility:public"], +) + +config_setting( + name = "is_linux_aarch64", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], + visibility = ["//visibility:public"], +) diff --git a/project_config.bzl b/project_config.bzl index f764a1d..431b883 100644 --- a/project_config.bzl +++ b/project_config.bzl @@ -1,5 +1,5 @@ # project_config.bzl PROJECT_CONFIG = { "asil_level": "QM", - "source_code": ["rust"], + "source_code": ["cpp", "rust", "python"], } diff --git a/score/crypto/api/BUILD b/score/crypto/api/BUILD new file mode 100644 index 0000000..2d29ce3 --- /dev/null +++ b/score/crypto/api/BUILD @@ -0,0 +1,27 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "operations", + visibility = [ + "//:__subpackages__", + ], + deps = [ + "//score/crypto/daemon/control_plane:control_operations", + "//score/crypto/daemon/key_management:key_management_operations", + "//score/crypto/daemon/mediator:mediator_operations", + "//score/crypto/daemon/provider/handler:hash_handler_operations", + "//score/crypto/daemon/provider/handler:mac_handler_operations", + ], +) diff --git a/score/crypto/api/control_plane/BUILD b/score/crypto/api/control_plane/BUILD new file mode 100644 index 0000000..63d9bb1 --- /dev/null +++ b/score/crypto/api/control_plane/BUILD @@ -0,0 +1,47 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "control_plane", + srcs = [ + ], + hdrs = [ + "connection_factory.hpp", + "i_connection.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/common:common_types", + "//score/crypto/daemon/common", + "//score/crypto/daemon/control_plane:request_handler_hdr", + ], +) + +cc_library( + name = "control_plane_impl", + srcs = [ + "src/connection_factory.cpp", + "src/connection_impl.cpp", + ], + hdrs = [ + "src/connection_impl.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [ + ":control_plane", + "//score/crypto/daemon/control_plane", + "//score/crypto/ipc:ipc_config", + "//score/crypto/ipc/grpc_adapter:grpc_control_client", + ], +) diff --git a/score/crypto/api/control_plane/connection_factory.hpp b/score/crypto/api/control_plane/connection_factory.hpp new file mode 100644 index 0000000..d96cce4 --- /dev/null +++ b/score/crypto/api/control_plane/connection_factory.hpp @@ -0,0 +1,47 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_API_CONTROLLPLANE_CONNECTION_FACTORY_HPP +#define SCORE_CRYPTO_API_CONTROLLPLANE_CONNECTION_FACTORY_HPP + +#include +#include + +#include "score/crypto/common/types.hpp" +#include "score/mw/crypto/api/common/error_domain.hpp" + +#include "score/crypto/api/control_plane/i_connection.hpp" + +namespace score::crypto::api::control_plane +{ + +/// Factory for creating control client nodes +/// Handles the creation and initialization of node connections to the daemon +class ConnectionFactory +{ + public: + ConnectionFactory() = default; + ~ConnectionFactory() = default; + + // Disable copy, allow move + ConnectionFactory(const ConnectionFactory&) = delete; + ConnectionFactory& operator=(const ConnectionFactory&) = delete; + ConnectionFactory(ConnectionFactory&&) = default; + ConnectionFactory& operator=(ConnectionFactory&&) = default; + + Expected, score::mw::crypto::CryptoErrorCode> CreateConnection( + std::string_view endpoint); +}; + +} // namespace score::crypto::api::control_plane + +#endif // SCORE_CRYPTO_API_CONTROLLPLANE_CONNECTION_FACTORY_HPP diff --git a/score/crypto/api/control_plane/i_connection.hpp b/score/crypto/api/control_plane/i_connection.hpp new file mode 100644 index 0000000..f929c2c --- /dev/null +++ b/score/crypto/api/control_plane/i_connection.hpp @@ -0,0 +1,53 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_API_CONTROL_PLANE_I_CONNECTION_HPP +#define SCORE_CRYPTO_API_CONTROL_PLANE_I_CONNECTION_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/mw/crypto/api/common/error_domain.hpp" + +namespace score::crypto::api::control_plane +{ + +class IConnection +{ + protected: + IConnection() = default; + + IConnection& operator=(const IConnection&) = delete; + IConnection(const IConnection&) = delete; + IConnection(IConnection&&) = default; + IConnection& operator=(IConnection&&) = default; + + public: + virtual ~IConnection() = default; + + virtual Expected SendRequest( + const daemon::control_plane::protocol::ControlRequest& request) = 0; + + /// @brief Returns the connection node ID assigned by the daemon during CONNECTION_OPEN. + /// @return DataNodeId that identifies this connection on the daemon side, or 0 if not yet established. + virtual daemon::control_plane::protocol::DataNodeId GetConnectionNodeId() const = 0; + + /// @brief Sets the connection node ID (called after successful CONNECTION_OPEN). + /// @param id The DataNodeId assigned by the daemon + virtual void SetConnectionNodeId(daemon::control_plane::protocol::DataNodeId id) + { + // Default no-op implementation for implementations that don't store the ID + } +}; + +} // namespace score::crypto::api::control_plane + +#endif // SCORE_CRYPTO_API_CONTROL_PLANE_I_CONNECTION_HPP diff --git a/score/crypto/api/control_plane/src/connection_factory.cpp b/score/crypto/api/control_plane/src/connection_factory.cpp new file mode 100644 index 0000000..a6b342e --- /dev/null +++ b/score/crypto/api/control_plane/src/connection_factory.cpp @@ -0,0 +1,52 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include +#include +#include + +#include "score/crypto/common/types.hpp" + +#include "score/crypto/api/control_plane/connection_factory.hpp" +#include "score/crypto/api/control_plane/i_connection.hpp" +#include "score/crypto/api/control_plane/src/connection_impl.hpp" +#include "score/mw/crypto/api/common/error_domain.hpp" + +namespace score::crypto::api::control_plane +{ + +Expected, score::mw::crypto::CryptoErrorCode> ConnectionFactory::CreateConnection( + std::string_view endpoint) +{ + const std::string_view unixProtocolPrefix = "unix://"; + if (endpoint.rfind(unixProtocolPrefix, 0) != 0) + { + std::cout << "[CONTROL_CLIENT_NODE_FACTORY] ERROR - Unsupported endpoint protocol. Only unix domain sockets " + "are supported." + << "\n"; + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + } + + const std::string_view socketPath = endpoint.substr(unixProtocolPrefix.size()); + + auto connection = std::make_unique(socketPath); + if (!connection) + { + std::cout << "[CONTROL_CLIENT_NODE_FACTORY] ERROR - Failed to create connection to socket: " << socketPath + << "\n"; + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInternalError); + } + + return connection; +} + +} // namespace score::crypto::api::control_plane diff --git a/score/crypto/api/control_plane/src/connection_impl.cpp b/score/crypto/api/control_plane/src/connection_impl.cpp new file mode 100644 index 0000000..9be9e35 --- /dev/null +++ b/score/crypto/api/control_plane/src/connection_impl.cpp @@ -0,0 +1,84 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include +#include +#include + +#include "score/crypto/api/control_plane/src/connection_impl.hpp" +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/control_plane/control_operations.h" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/ipc/grpc_adapter/grpc_control_client.h" +#include "score/mw/crypto/api/common/error_domain.hpp" + +namespace score::crypto::api::control_plane +{ + +ConnectionImpl::ConnectionImpl(std::string_view endpoint) : mClient(std::make_unique(endpoint)) +{ +} + +ConnectionImpl::~ConnectionImpl() +{ + // Send CONNECTION_CLOSE when the last reference to this connection is destroyed + if (m_connection_id == 0) + { + return; // Connection not fully opened + } + + namespace proto = ::score::crypto::daemon::control_plane::protocol; + namespace ctrl_ops = ::score::crypto::daemon::control_plane::operations; + + auto requestRes = + proto::ControlRequestBuilder().forDataNodeId(m_connection_id).operation(ctrl_ops::CloseConnection()).build(); + + if (!requestRes.has_value()) + { + std::cerr << "[IPC][ConnectionImpl] ERROR: Failed to build CONNECTION_CLOSE request\n"; + return; + } + + auto responseRes = mClient->SendRequest(requestRes.value()); + + auto validator = proto::ControlResponseValidator::FromResult(responseRes); + validator.expectOperation(ctrl_ops::CloseConnection()).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[IPC][ConnectionImpl] ERROR: CONNECTION_CLOSE response validation failed: " + << validator.getError() << "\n"; + return; + } +} + +Expected +ConnectionImpl::SendRequest(const daemon::control_plane::protocol::ControlRequest& request) +{ + // HINT: We expect, that the IPC + // - Ensures Blocking behaviour (RPC style) + // - Ensures Responses are matching the Request + // - Enhances the request with UserId and RequestId + return mClient->SendRequest(request); +} + +daemon::control_plane::protocol::DataNodeId ConnectionImpl::GetConnectionNodeId() const +{ + return m_connection_id; +} + +void ConnectionImpl::SetConnectionNodeId(daemon::control_plane::protocol::DataNodeId id) +{ + m_connection_id = id; +} + +} // namespace score::crypto::api::control_plane diff --git a/score/crypto/api/control_plane/src/connection_impl.hpp b/score/crypto/api/control_plane/src/connection_impl.hpp new file mode 100644 index 0000000..0d5483a --- /dev/null +++ b/score/crypto/api/control_plane/src/connection_impl.hpp @@ -0,0 +1,55 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_API_CONTROL_PLANE_CONNECTION_IMPL_H +#define SCORE_CRYPTO_API_CONTROL_PLANE_CONNECTION_IMPL_H + +#include +#include + +#include "score/crypto/api/control_plane/i_connection.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/ipc/grpc_adapter/grpc_control_client.h" + +namespace score::crypto::api::control_plane +{ + +/// Control plane client implementation - communicates with daemon's control server +class ConnectionImpl : public IConnection +{ + public: + Expected SendRequest( + const daemon::control_plane::protocol::ControlRequest& request) override; + + daemon::control_plane::protocol::DataNodeId GetConnectionNodeId() const override; + + explicit ConnectionImpl(std::string_view endpoint); + + ~ConnectionImpl() override; + + /// Set the connection node ID after successful CONNECTION_OPEN + void SetConnectionNodeId(daemon::control_plane::protocol::DataNodeId id) override; + + /// Disable copy, allow move + ConnectionImpl(const ConnectionImpl&) = delete; + ConnectionImpl& operator=(const ConnectionImpl&) = delete; + ConnectionImpl(ConnectionImpl&&) = default; + ConnectionImpl& operator=(ConnectionImpl&&) = default; + + private: + std::shared_ptr mClient; + daemon::control_plane::protocol::DataNodeId m_connection_id = 0; +}; + +} // namespace score::crypto::api::control_plane + +#endif // SCORE_CRYPTO_API_CONTROL_PLANE_CONNECTION_IMPL_H diff --git a/score/crypto/common/BUILD b/score/crypto/common/BUILD new file mode 100644 index 0000000..c64d1ca --- /dev/null +++ b/score/crypto/common/BUILD @@ -0,0 +1,31 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +cc_library( + name = "common_types", + srcs = [ + "types.hpp", + ], + copts = [ + # Use baselibs expected implementation by default + "-DSTD_LIB_CPP17=0", + ], + visibility = ["//:__subpackages__"], + # Default: use baselibs/futurecpp expected + # To switch to C++17 std lib: remove the deps below and set -DSTD_LIB_CPP17=1 + deps = [ + # Use results for deatils/expected, as it is not directly visible + "@score_baselibs//score/result", + ], +) diff --git a/score/crypto/common/types.hpp b/score/crypto/common/types.hpp new file mode 100644 index 0000000..259ea00 --- /dev/null +++ b/score/crypto/common/types.hpp @@ -0,0 +1,164 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_COMMON_TYPES_HPP +#define SCORE_CRYPTO_COMMON_TYPES_HPP + +#include + +#include "score/span.hpp" + +#if STD_LIB_CPP17 == 0 +#include "score/result/details/expected/expected.h" +#else +#include +#endif + +namespace score +{ +namespace crypto +{ + +#if STD_LIB_CPP17 == 1 + +// C++17 standard library implementation using std::variant +template +class Unexpected +{ + private: + E error_; + + public: + explicit Unexpected(E error) : error_(std::move(error)) {} + const E& value() const + { + return error_; + } +}; + +template +Unexpected make_unexpected(E error) +{ + return Unexpected(std::move(error)); +} + +// C++17 implementation of expected using std::variant +template +class Expected +{ + private: + std::variant m_data; + + public: + // Constructors for success case + Expected(T value) : m_data(std::move(value)) {} + + // Constructor for error case using unexpected + Expected(Unexpected err) : m_data(std::move(err.value())) {} + + // Implicit conversion constructor from any type convertible to T + template && !std::is_same_v>>> + Expected(U&& value) : m_data(T(std::forward(value))) + { + } + + // Boolean operator - returns true if contains value + explicit operator bool() const + { + return std::holds_alternative(m_data); + } + + // Check if contains value + bool has_value() const + { + return std::holds_alternative(m_data); + } + + // Get value (throws if contains error) + T& value() + { + if (!has_value()) + { + throw std::bad_variant_access(); + } + return std::get(m_data); + } + + const T& value() const + { + if (!has_value()) + { + throw std::bad_variant_access(); + } + return std::get(m_data); + } + + // Get error + E& error() + { + if (has_value()) + { + throw std::bad_variant_access(); + } + return std::get(m_data); + } + + const E& error() const + { + if (has_value()) + { + throw std::bad_variant_access(); + } + return std::get(m_data); + } + + // Move value out + T&& operator*() && + { + return std::move(std::get(m_data)); + } + + // Reference to value + T& operator*() & + { + return std::get(m_data); + } + + const T& operator*() const& + { + return std::get(m_data); + } +}; + +#else + +// Use score::details::expected from baselibs/futurecpp directly +// Simple type aliases - no wrappers needed +template +using Expected = score::details::expected; + +// Forward to score::cpp::unexpected +template +auto make_unexpected(E error) +{ + return score::details::unexpected(std::move(error)); +} + +template +using span = score::cpp::span; + +#endif + +} // namespace crypto +} // namespace score + +#endif // SCORE_CRYPTO_COMMON_TYPES_HPP diff --git a/score/crypto/daemon/BUILD b/score/crypto/daemon/BUILD new file mode 100644 index 0000000..77913cd --- /dev/null +++ b/score/crypto/daemon/BUILD @@ -0,0 +1,58 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_binary") + +cc_binary( + name = "crypto_daemon", + srcs = ["src/daemon.cpp"], + copts = select({ + "//conditions:default": [], + ":tsan_enabled": [ + "-fsanitize=thread", + "-g", + "-O1", + ], + }), + dynamic_deps = [ + "//third_party/openssl:openssl_shared", + "//third_party/grpc:libgrpc_shared", + ], + includes = ["IPC"], + linkopts = select({ + "//conditions:default": [], + ":tsan_enabled": [ + "-fsanitize=thread", + ], + }), + linkstatic = True, + visibility = ["//tests:__subpackages__"], + deps = [ + "//score/crypto/daemon/config", + "//score/crypto/daemon/control_plane", + "//score/crypto/daemon/data_manager", + "//score/crypto/daemon/mediator", + "//score/crypto/daemon/provider:provider_manager", + "//score/crypto/daemon/provider/pkcs11:provider_pkcs11_factory", + "//score/crypto/daemon/provider/score_provider:score_provider_factory", + "//score/crypto/ipc:ipc_config", + "//score/crypto/ipc/grpc_adapter:grpc_control_server", + ], +) + +# Configuration for enabling ThreadSanitizer +config_setting( + name = "tsan_enabled", + values = { + "define": "tsan=1", + }, +) diff --git a/score/crypto/daemon/common/BUILD b/score/crypto/daemon/common/BUILD new file mode 100644 index 0000000..1f358cf --- /dev/null +++ b/score/crypto/daemon/common/BUILD @@ -0,0 +1,49 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +cc_library( + name = "common", + hdrs = [ + "actors.hpp", + "daemon_error.hpp", + "secure_memory.hpp", + "types.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [ + "//score/mw/crypto/api/common:crypto_common", + "@score_baselibs//score/result", + ], +) + +# Header-only utility: algorithm sizing tables for hash, MAC, and key algorithms. +cc_library( + name = "algorithm_info", + hdrs = ["algorithm_info.hpp"], + visibility = ["//:__subpackages__"], +) + +# Header-only utility: human-readable names for OperationActor / OperationAction values. +# Depends on the three operation-constants headers (each only deps on :common). +cc_library( + name = "operation_names", + hdrs = ["operation_names.hpp"], + visibility = ["//:__subpackages__"], + deps = [ + ":common", + "//score/crypto/daemon/key_management:key_management_operations", + "//score/crypto/daemon/provider/handler:hash_handler_operations", + "//score/crypto/daemon/provider/handler:mac_handler_operations", + ], +) diff --git a/score/crypto/daemon/common/actors.hpp b/score/crypto/daemon/common/actors.hpp new file mode 100644 index 0000000..7038796 --- /dev/null +++ b/score/crypto/daemon/common/actors.hpp @@ -0,0 +1,35 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_COMMON_OPERATION_ACTORS_HPP +#define SCORE_CRYPTO_DAEMON_COMMON_OPERATION_ACTORS_HPP + +#include + +#include "score/crypto/daemon/common/types.hpp" + +namespace score::crypto::daemon::common::actors +{ + +inline constexpr OperationActor OP_ACTOR_CONTROL = 1; +inline constexpr OperationActor OP_ACTOR_MEDIATOR = 2; +inline constexpr OperationActor OP_ACTOR_PROVIDER = 3; +inline constexpr OperationActor OP_ACTOR_HASH_HANDLER = 4; +inline constexpr OperationActor OP_ACTOR_KEY_MANAGEMENT = 5; +inline constexpr OperationActor OP_ACTOR_MAC_HANDLER = 6; + +// Starting point for custom actors +inline constexpr OperationActor CUSTOM_ACTOR_START = 1 << (std::numeric_limits::digits - 1); + +} // namespace score::crypto::daemon::common::actors + +#endif // SCORE_CRYPTO_DAEMON_COMMON_OPERATION_ACTORS_HPP diff --git a/score/crypto/daemon/common/algorithm_info.hpp b/score/crypto/daemon/common/algorithm_info.hpp new file mode 100644 index 0000000..c1dedef --- /dev/null +++ b/score/crypto/daemon/common/algorithm_info.hpp @@ -0,0 +1,126 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_DAEMON_COMMON_ALGORITHM_INFO_HPP +#define CRYPTO_DAEMON_COMMON_ALGORITHM_INFO_HPP + +#include +#include +#include + +namespace score::crypto::daemon::common +{ + +// --------------------------------------------------------------------------- +// Hash algorithm properties (provider-independent) +// --------------------------------------------------------------------------- + +struct HashAlgorithmInfo +{ + std::string_view name; + std::size_t digest_size; ///< Output size in bytes +}; + +inline constexpr HashAlgorithmInfo kHashAlgorithms[] = { + {"SHA256", 32U}, + {"SHA384", 48U}, + {"SHA512", 64U}, + {"SHA224", 28U}, + {"SHA1", 20U}, + {"MD5", 16U}, +}; + +/// @brief Look up digest size by algorithm name. +/// @return digest size in bytes, or std::nullopt if unknown. +[[nodiscard]] inline constexpr std::optional LookupDigestSize(std::string_view algorithm) noexcept +{ + for (const auto& entry : kHashAlgorithms) + { + if (entry.name == algorithm) + { + return entry.digest_size; + } + } + return std::nullopt; +} + +// --------------------------------------------------------------------------- +// MAC algorithm properties (provider-independent) +// --------------------------------------------------------------------------- + +struct MacAlgorithmInfo +{ + std::string_view name; + std::size_t mac_size; ///< Output tag size in bytes +}; + +inline constexpr MacAlgorithmInfo kMacAlgorithms[] = { + {"HMAC-SHA256", 32U}, + {"HMAC-SHA384", 48U}, + {"HMAC-SHA512", 64U}, +}; + +/// @brief Look up MAC output size by algorithm name. +/// @return MAC size in bytes, or std::nullopt if unknown. +[[nodiscard]] inline constexpr std::optional LookupMacSize(std::string_view algorithm) noexcept +{ + for (const auto& entry : kMacAlgorithms) + { + if (entry.name == algorithm) + { + return entry.mac_size; + } + } + return std::nullopt; +} + +// --------------------------------------------------------------------------- +// Key algorithm properties (provider-independent) +// --------------------------------------------------------------------------- + +struct KeyAlgorithmInfo +{ + std::string_view name; + std::size_t key_size; ///< Default key size in bytes +}; + +inline constexpr KeyAlgorithmInfo kKeyAlgorithms[] = { + {"HMAC-SHA256", 32U}, + {"HMAC-SHA384", 48U}, + {"HMAC-SHA512", 64U}, + {"AES-128-CBC", 16U}, + {"AES-192-CBC", 24U}, + {"AES-256-CBC", 32U}, + {"AES-128-GCM", 16U}, + {"AES-192-GCM", 24U}, + {"AES-256-GCM", 32U}, + {"AES-128-CMAC", 16U}, + {"AES-256-CMAC", 32U}, +}; + +/// @brief Look up default key size by algorithm name. +/// @return key size in bytes, or std::nullopt if unknown. +[[nodiscard]] inline constexpr std::optional LookupKeySize(std::string_view algorithm) noexcept +{ + for (const auto& entry : kKeyAlgorithms) + { + if (entry.name == algorithm) + { + return entry.key_size; + } + } + return std::nullopt; +} + +} // namespace score::crypto::daemon::common + +#endif // CRYPTO_DAEMON_COMMON_ALGORITHM_INFO_HPP diff --git a/score/crypto/daemon/common/daemon_error.hpp b/score/crypto/daemon/common/daemon_error.hpp new file mode 100644 index 0000000..d062f8b --- /dev/null +++ b/score/crypto/daemon/common/daemon_error.hpp @@ -0,0 +1,284 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_COMMON_DAEMON_ERROR_HPP +#define SCORE_CRYPTO_DAEMON_COMMON_DAEMON_ERROR_HPP + +// Forward-declaration of the translation target; only ToCryptoErrorCode() needs +// the full CryptoErrorCode definition. +#include "score/mw/crypto/api/common/error_domain.hpp" + +#include + +namespace score::crypto::daemon::common +{ + +/// @brief Complete, self-contained error code vocabulary for the crypto daemon. +/// +/// All daemon-internal code (handlers, executors, key-management, data-manager) +/// uses ONLY this enum — never score::crypto::daemon::common::DaemonErrorCode directly. +/// This makes the daemon independent of the library API version: if CryptoErrorCode +/// values or names change in a future library release, only ToCryptoErrorCode() +/// below needs updating, not the daemon implementation files. +/// +/// Numeric values are stable daemon-private identifiers (0x01xx–0x0Bxx ranges +/// mirroring the API categories, 0x10xx for daemon-only codes). They are never +/// used as wire values; the IPC boundary converts to CryptoErrorCode via +/// ToCryptoErrorCode() before encoding OperationResult. +enum class DaemonErrorCode : std::uint32_t +{ + // ---- General / initialization ---- + kUninitializedStack = 0x0101, ///< Daemon (or underlying stack) not initialised + kAlreadyInitialized = 0x0102, ///< Daemon (or stack) already initialised + kConnectionFailed = 0x0103, ///< IPC channel could not be established + kInternalError = 0x0104, ///< Unspecified daemon-internal error + + // ---- Operation ---- + kUnsupportedOperation = 0x0201, ///< Requested operation is not supported by the provider + kInvalidOperation = 0x0202, ///< Operation not valid in the current context state + kOperationFailed = 0x0203, ///< Operation execution failed + kOperationTimedOut = 0x0204, ///< Operation exceeded its deadline + + // ---- Parameter / validation ---- + kInvalidArgument = 0x0301, ///< Argument value is out of range or malformed + kInvalidResourceId = 0x0302, ///< Resource identifier does not resolve to a known resource + kInvalidResourceType = 0x0303, ///< Resource type does not match the expected type + kInsufficientBufferSize = 0x0304, ///< Output buffer provided by the caller is too small + kInvalidFormat = 0x0305, ///< Data format not recognised (e.g. DER/PEM) + kParamTruncated = 0x0306, ///< Parameter silently truncated (exceeds fixed-capacity storage) + + // ---- Streaming ---- + kStreamNotInitialized = 0x0401, ///< Init() not called before Update()/Finalize() + kStreamAlreadyActive = 0x0402, ///< Init() called while a stream is already active + kStreamIncomplete = 0x0403, ///< Finalize() called without sufficient input data + + // ---- Algorithm ---- + kUnsupportedAlgorithm = 0x0501, ///< Algorithm not supported by any configured provider + kAlgorithmMismatch = 0x0502, ///< Algorithm is incompatible with the key or slot + + // ---- Memory / resource ---- + kAllocationFailed = 0x0601, ///< Shared-memory or heap allocation failed + kQuotaExceeded = 0x0602, ///< Per-application resource quota exceeded + kInvalidMemoryRegion = 0x0603, ///< Memory region is invalid or already released + + // ---- Context ---- + kContextCreationFailed = 0x0701, ///< Failed to create an operation context + kContextAlreadyDestroyed = 0x0702, ///< Context used after destruction + kContextResetFailed = 0x0703, ///< Failed to reset context to initial state + kSessionExpired = 0x0704, ///< Session sentinel expired (context was bulk-destroyed) + + // ---- Key management ---- + kKeySlotEmpty = 0x0801, ///< Key slot contains no key material + kKeySlotOccupied = 0x0802, ///< Key slot already occupied + kKeyNotExportable = 0x0803, ///< Key cannot be exported from its provider + kKeyGenerationFailed = 0x0804, ///< Key generation failed + kKeyDerivationFailed = 0x0805, ///< Key derivation failed + kKeyAgreementFailed = 0x0806, ///< Key agreement failed + kWrapUnwrapFailed = 0x0807, ///< Key wrap/unwrap operation failed + kPersistFailed = 0x0808, ///< Failed to persist ephemeral key + kIncompatibleKeyType = 0x0809, ///< Key type is incompatible with the requested operation + kKeyOperationNotPermitted = 0x080A, ///< Key's permitted-operations policy forbids this use + + // ---- Certificate ---- + kCertificateParsingFailed = 0x0901, + kCertificateExpired = 0x0902, + kCertificateRevoked = 0x0903, + kCertificateVerifyFailed = 0x0904, + kCertChainVerifyFailed = 0x0905, + kCrlImportFailed = 0x0906, + kCsrGenerationFailed = 0x0907, + kOcspError = 0x0908, + kTrustAnchorNotFound = 0x0909, + + // ---- Provider ---- + kProviderNotAvailable = 0x0A01, + kProviderBusy = 0x0A02, + kCrossProviderIncompatible = 0x0A03, + + // ---- Access control ---- + kAccessDenied = 0x0B01, + kResourceNotAllocated = 0x0B02, + + // ---- Daemon-internal only (no direct single-concept CryptoErrorCode equivalent) ---- + // These codes carry finer-grained internal detail that is translated down to a + // broader public code at the ToCryptoErrorCode() boundary. + kInvalidState = 0x1001, ///< Internal state machine is in an unexpected state + kOperationInProgress = 0x1002, ///< A conflicting operation is already running internally + kInvalidContext = 0x1003, ///< Internal context handle is null or corrupted + kInsufficientParameters = 0x1004, ///< A required internal parameter is absent + kInvalidDataType = 0x1005, ///< Parameter variant holds an unexpected alternative type + kInvalidStreamOperation = 0x1006, ///< Stream operation issued in the wrong internal sequence + kAlgorithmInitializationFailed = 0x1007, ///< Crypto algorithm context could not be set up + kAlgorithmExecutionFailed = 0x1008, ///< Crypto algorithm operation returned an error + kResourceBusy = 0x1009, ///< An internal resource is locked by another operation + kContextCleanupFailed = 0x100A, ///< Internal context resources could not be fully released + kSessionInvalid = 0x100B, ///< PKCS#11 session is no longer valid (token removed, HSM error) + kUnknown = 0xFFFF, ///< Unclassified daemon-internal error +}; + +/// @brief Translates a daemon-internal error code to the closest public API error code. +/// +/// This is the **sole** translation point between daemon internals and the +/// library-visible API. All make_unexpected() calls in daemon code use +/// DaemonErrorCode. Only the IPC boundary (control_protocol.h return_error, +/// mediator) calls this function to encode the wire OperationResult. +/// +/// When CryptoErrorCode evolves in a later library version, update only this +/// function — no daemon handler or executor files need to change. +inline score::mw::crypto::CryptoErrorCode ToCryptoErrorCode(DaemonErrorCode code) noexcept +{ + using C = score::mw::crypto::CryptoErrorCode; + switch (code) + { + // ---- General ---- + case DaemonErrorCode::kUninitializedStack: + return C::kUninitializedStack; + case DaemonErrorCode::kAlreadyInitialized: + return C::kAlreadyInitialized; + case DaemonErrorCode::kConnectionFailed: + return C::kConnectionFailed; + case DaemonErrorCode::kInternalError: + return C::kInternalError; + // ---- Operation ---- + case DaemonErrorCode::kUnsupportedOperation: + return C::kUnsupportedOperation; + case DaemonErrorCode::kInvalidOperation: + return C::kInvalidOperation; + case DaemonErrorCode::kOperationFailed: + return C::kOperationFailed; + case DaemonErrorCode::kOperationTimedOut: + return C::kOperationTimedOut; + // ---- Parameter ---- + case DaemonErrorCode::kInvalidArgument: + return C::kInvalidArgument; + case DaemonErrorCode::kInvalidResourceId: + return C::kInvalidResourceId; + case DaemonErrorCode::kInvalidResourceType: + return C::kInvalidResourceType; + case DaemonErrorCode::kInsufficientBufferSize: + return C::kInsufficientBufferSize; + case DaemonErrorCode::kInvalidFormat: + return C::kInvalidFormat; + case DaemonErrorCode::kParamTruncated: + return C::kParamTruncated; + // ---- Streaming ---- + case DaemonErrorCode::kStreamNotInitialized: + return C::kStreamNotInitialized; + case DaemonErrorCode::kStreamAlreadyActive: + return C::kStreamAlreadyActive; + case DaemonErrorCode::kStreamIncomplete: + return C::kStreamIncomplete; + // ---- Algorithm ---- + case DaemonErrorCode::kUnsupportedAlgorithm: + return C::kUnsupportedAlgorithm; + case DaemonErrorCode::kAlgorithmMismatch: + return C::kAlgorithmMismatch; + // ---- Memory / resource ---- + case DaemonErrorCode::kAllocationFailed: + return C::kAllocationFailed; + case DaemonErrorCode::kQuotaExceeded: + return C::kQuotaExceeded; + case DaemonErrorCode::kInvalidMemoryRegion: + return C::kInvalidMemoryRegion; + // ---- Context ---- + case DaemonErrorCode::kContextCreationFailed: + return C::kContextCreationFailed; + case DaemonErrorCode::kContextAlreadyDestroyed: + return C::kContextAlreadyDestroyed; + case DaemonErrorCode::kContextResetFailed: + return C::kContextResetFailed; + case DaemonErrorCode::kSessionExpired: + return C::kSessionExpired; + // ---- Key management ---- + case DaemonErrorCode::kKeySlotEmpty: + return C::kKeySlotEmpty; + case DaemonErrorCode::kKeySlotOccupied: + return C::kKeySlotOccupied; + case DaemonErrorCode::kKeyNotExportable: + return C::kKeyNotExportable; + case DaemonErrorCode::kKeyGenerationFailed: + return C::kKeyGenerationFailed; + case DaemonErrorCode::kKeyDerivationFailed: + return C::kKeyDerivationFailed; + case DaemonErrorCode::kKeyAgreementFailed: + return C::kKeyAgreementFailed; + case DaemonErrorCode::kWrapUnwrapFailed: + return C::kWrapUnwrapFailed; + case DaemonErrorCode::kPersistFailed: + return C::kPersistFailed; + case DaemonErrorCode::kIncompatibleKeyType: + return C::kIncompatibleKeyType; + case DaemonErrorCode::kKeyOperationNotPermitted: + return C::kKeyOperationNotPermitted; + // ---- Certificate ---- + case DaemonErrorCode::kCertificateParsingFailed: + return C::kCertificateParsingFailed; + case DaemonErrorCode::kCertificateExpired: + return C::kCertificateExpired; + case DaemonErrorCode::kCertificateRevoked: + return C::kCertificateRevoked; + case DaemonErrorCode::kCertificateVerifyFailed: + return C::kCertificateVerifyFailed; + case DaemonErrorCode::kCertChainVerifyFailed: + return C::kCertChainVerifyFailed; + case DaemonErrorCode::kCrlImportFailed: + return C::kCrlImportFailed; + case DaemonErrorCode::kCsrGenerationFailed: + return C::kCsrGenerationFailed; + case DaemonErrorCode::kOcspError: + return C::kOcspError; + case DaemonErrorCode::kTrustAnchorNotFound: + return C::kTrustAnchorNotFound; + // ---- Provider ---- + case DaemonErrorCode::kProviderNotAvailable: + return C::kProviderNotAvailable; + case DaemonErrorCode::kProviderBusy: + return C::kProviderBusy; + case DaemonErrorCode::kCrossProviderIncompatible: + return C::kCrossProviderIncompatible; + // ---- Access control ---- + case DaemonErrorCode::kAccessDenied: + return C::kAccessDenied; + case DaemonErrorCode::kResourceNotAllocated: + return C::kResourceNotAllocated; + // ---- Daemon-internal (translated to broader public codes) ---- + case DaemonErrorCode::kInvalidState: + return C::kInvalidOperation; + case DaemonErrorCode::kOperationInProgress: + return C::kInvalidOperation; + case DaemonErrorCode::kInvalidContext: + return C::kInvalidOperation; + case DaemonErrorCode::kInsufficientParameters: + return C::kInvalidArgument; + case DaemonErrorCode::kInvalidDataType: + return C::kInvalidArgument; + case DaemonErrorCode::kInvalidStreamOperation: + return C::kInvalidOperation; + case DaemonErrorCode::kAlgorithmInitializationFailed: + return C::kOperationFailed; + case DaemonErrorCode::kAlgorithmExecutionFailed: + return C::kOperationFailed; + case DaemonErrorCode::kResourceBusy: + return C::kOperationFailed; + case DaemonErrorCode::kContextCleanupFailed: + return C::kOperationFailed; + case DaemonErrorCode::kSessionInvalid: + return C::kSessionExpired; + case DaemonErrorCode::kUnknown: + return C::kInternalError; + } + return C::kInternalError; +} + +} // namespace score::crypto::daemon::common + +#endif // SCORE_CRYPTO_DAEMON_COMMON_DAEMON_ERROR_HPP diff --git a/score/crypto/daemon/common/operation_names.hpp b/score/crypto/daemon/common/operation_names.hpp new file mode 100644 index 0000000..06dba15 --- /dev/null +++ b/score/crypto/daemon/common/operation_names.hpp @@ -0,0 +1,217 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_COMMON_OPERATION_NAMES_HPP +#define SCORE_CRYPTO_DAEMON_COMMON_OPERATION_NAMES_HPP + +/// @file operation_names.hpp +/// @brief Human-readable names for OperationActor and OperationAction values. +/// +/// Provides constexpr lookup functions and a stream-insertable wrapper so that +/// daemon log messages display symbolic operation names instead of raw integers. +/// +/// ### Usage +/// @code +/// // Instead of: +/// std::cout << "action=" << opId.operationAction << "\n"; +/// +/// // Write: +/// std::cout << common::OpId{opId} << "\n"; +/// // Output: "HASH_HANDLER::HASH_FINISH [actor=4, action=3]" +/// @endcode +/// +/// ### Design notes +/// - Header-only: all functions are constexpr, no runtime tables. +/// - Covers all first-party actors and their operations. +/// - Falls back to "" / "" for future or custom values, +/// while still printing the numeric ids so nothing is lost. + +#include "score/crypto/daemon/common/actors.hpp" +#include "score/crypto/daemon/common/types.hpp" + +// Operation constants — these headers only depend on types.hpp (no circular risk). +#include "score/crypto/daemon/key_management/interfaces/key_management_operations.hpp" +#include "score/crypto/daemon/provider/handler/operations/hash_handler_operations.hpp" +#include "score/crypto/daemon/provider/handler/operations/mac_handler_operations.hpp" + +#include +#include + +namespace score::crypto::daemon::common +{ + +/// @brief Returns the symbolic name for a registered OperationActor value. +constexpr std::string_view ActorName(OperationActor actor) noexcept +{ + switch (actor) + { + case actors::OP_ACTOR_CONTROL: + return "CONTROL"; + case actors::OP_ACTOR_MEDIATOR: + return "MEDIATOR"; + case actors::OP_ACTOR_PROVIDER: + return "PROVIDER"; + case actors::OP_ACTOR_HASH_HANDLER: + return "HASH_HANDLER"; + case actors::OP_ACTOR_KEY_MANAGEMENT: + return "KEY_MGMT"; + case actors::OP_ACTOR_MAC_HANDLER: + return "MAC_HANDLER"; + default: + return ""; + } +} + +/// @brief Returns the symbolic name for an OperationAction within the given actor's namespace. +/// +/// The actor context is required because the same action integer has different meanings +/// across actors (e.g., action=1 is CTX_CREATE for MEDIATOR but HASH_INIT for HASH_HANDLER). +constexpr std::string_view ActionName(OperationActor actor, OperationAction action) noexcept +{ + // Mediator action constants (mediator_operations.hpp cannot be included here without + // pulling in control_protocol.h — the values are stable and documented there). + constexpr OperationAction kMediatorCtxCreate = 1; + constexpr OperationAction kMediatorCtxClose = 2; + constexpr OperationAction kMediatorResolveResource = 3; + + namespace hash_ops = provider::handler::hash_handler_operations; + namespace mac_ops = provider::handler::mac_handler_operations; + namespace km_ops = key_management::operations; + + switch (actor) + { + case actors::OP_ACTOR_MEDIATOR: + switch (action) + { + case kMediatorCtxCreate: + return "CTX_CREATE"; + case kMediatorCtxClose: + return "CTX_CLOSE"; + case kMediatorResolveResource: + return "RESOLVE_RESOURCE"; + default: + return ""; + } + + case actors::OP_ACTOR_HASH_HANDLER: + switch (action) + { + case hash_ops::HASH_INIT: + return "HASH_INIT"; + case hash_ops::HASH_UPDATE: + return "HASH_UPDATE"; + case hash_ops::HASH_FINISH: + return "HASH_FINISH"; + case hash_ops::HASH_SS: + return "HASH_SS"; + case hash_ops::HASH_GET_DIGEST_SIZE: + return "HASH_GET_DIGEST_SIZE"; + case hash_ops::HASH_RESET: + return "HASH_RESET"; + default: + return ""; + } + + case actors::OP_ACTOR_MAC_HANDLER: + switch (action) + { + case mac_ops::MAC_INIT: + return "MAC_INIT"; + case mac_ops::MAC_UPDATE: + return "MAC_UPDATE"; + case mac_ops::MAC_FINAL: + return "MAC_FINAL"; + case mac_ops::MAC_VERIFY: + return "MAC_VERIFY"; + case mac_ops::MAC_GET_SIZE: + return "MAC_GET_SIZE"; + case mac_ops::MAC_RESET: + return "MAC_RESET"; + case mac_ops::MAC_SS: + return "MAC_SS"; + default: + return ""; + } + + case actors::OP_ACTOR_KEY_MANAGEMENT: + switch (action) + { + case km_ops::KEY_GENERATE: + return "KEY_GENERATE"; + case km_ops::KEY_GENERATE_TO_SLOT: + return "KEY_GENERATE_TO_SLOT"; + case km_ops::KEY_PERSIST: + return "KEY_PERSIST"; + case km_ops::KEY_DERIVE: + return "KEY_DERIVE"; + case km_ops::KEY_DERIVE_TO_SLOT: + return "KEY_DERIVE_TO_SLOT"; + case km_ops::KEY_AGREE: + return "KEY_AGREE"; + case km_ops::KEY_AGREE_TO_SLOT: + return "KEY_AGREE_TO_SLOT"; + case km_ops::KEY_WRAP: + return "KEY_WRAP"; + case km_ops::KEY_GET_WRAP_SIZE: + return "KEY_GET_WRAP_SIZE"; + case km_ops::KEY_UNWRAP: + return "KEY_UNWRAP"; + case km_ops::KEY_UNWRAP_TO_SLOT: + return "KEY_UNWRAP_TO_SLOT"; + case km_ops::KEY_IMPORT: + return "KEY_IMPORT"; + case km_ops::KEY_IMPORT_TO_SLOT: + return "KEY_IMPORT_TO_SLOT"; + case km_ops::KEY_LOAD: + return "KEY_LOAD"; + case km_ops::KEY_EXPORT: + return "KEY_EXPORT"; + case km_ops::KEY_GET_EXPORT_SIZE: + return "KEY_GET_EXPORT_SIZE"; + case km_ops::KEY_SLOT_INFO: + return "KEY_SLOT_INFO"; + case km_ops::KEY_CLEAR: + return "KEY_CLEAR"; + case km_ops::KEY_RELEASE: + return "KEY_RELEASE"; + default: + return ""; + } + + default: + return ""; + } +} + +/// @brief Lightweight stream-insertable wrapper for OperationIdentifier. +/// +/// Prints both the symbolic name and the numeric ids so logs are unambiguous even +/// for custom or future operations not yet registered in ActionName(). +/// +/// @code +/// std::cout << "[MED] dispatching " << common::OpId{opId} << "\n"; +/// // → "[MED] dispatching HASH_HANDLER::HASH_UPDATE [actor=4, action=2]" +/// @endcode +struct OpId +{ + const OperationIdentifier& id; +}; + +inline std::ostream& operator<<(std::ostream& os, const OpId& op) +{ + return os << ActorName(op.id.operationActor) << "::" << ActionName(op.id.operationActor, op.id.operationAction) + << " [actor=" << op.id.operationActor << ", action=" << op.id.operationAction << "]"; +} + +} // namespace score::crypto::daemon::common + +#endif // SCORE_CRYPTO_DAEMON_COMMON_OPERATION_NAMES_HPP diff --git a/score/crypto/daemon/common/secure_memory.hpp b/score/crypto/daemon/common/secure_memory.hpp new file mode 100644 index 0000000..70fe60b --- /dev/null +++ b/score/crypto/daemon/common/secure_memory.hpp @@ -0,0 +1,75 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_COMMON_SECURE_MEMORY_HPP +#define SCORE_CRYPTO_DAEMON_COMMON_SECURE_MEMORY_HPP + +#include +#include +#include + +#if defined(__linux__) +#include // explicit_bzero +#elif defined(__QNX__) +#include // _NTO_VERSION +#if _NTO_VERSION >= 700 +#include // memset_s +#endif +#endif + +namespace score::crypto::daemon::common +{ + +/// @brief Securely zeroize memory. Guaranteed not to be optimized away. +/// +/// Uses platform-specific primitives: +/// - Linux: explicit_bzero +/// - QNX 7.0+: memset_s +/// - Fallback: manual loop with volatile ptr +/// +/// No provider dependencies (no OpenSSL, PKCS#11, etc.). +inline void SecureZeroize(void* ptr, std::size_t len) noexcept +{ + if (ptr == nullptr || len == 0U) + { + return; + } +#if defined(__linux__) + explicit_bzero(ptr, len); +#elif defined(__QNX__) && (_NTO_VERSION >= 700) + memset_s(ptr, len, 0, len); +#else + // Volatile pointer prevents compiler from optimizing away the memset + volatile unsigned char* p = static_cast(ptr); + while (len-- > 0U) + { + *p++ = 0; + } +#endif +} + +/// @brief Securely zeroize a std::vector and then clear it. +/// +/// Zeroizes the buffer contents before clearing the vector, ensuring +/// sensitive data does not remain in freed heap memory. +inline void SecureZeroizeAndClear(std::vector& v) noexcept +{ + if (!v.empty()) + { + SecureZeroize(v.data(), v.size()); + v.clear(); + } +} + +} // namespace score::crypto::daemon::common + +#endif // SCORE_CRYPTO_DAEMON_COMMON_SECURE_MEMORY_HPP diff --git a/score/crypto/daemon/common/types.hpp b/score/crypto/daemon/common/types.hpp new file mode 100644 index 0000000..2858752 --- /dev/null +++ b/score/crypto/daemon/common/types.hpp @@ -0,0 +1,175 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_COMMON_TYPES_HPP +#define SCORE_CRYPTO_DAEMON_COMMON_TYPES_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::common +{ + +using HandlerId = std::string; +using AlgorithmId = std::string; +// TODO: MediatorId shall use numeric type +using MediatorId = std::string; + +// ============================================================================ +// Provider Identity Types +// ============================================================================ +// ProviderName: Human-readable identifier used at configuration and setup time +using ProviderName = std::string; + +// ProviderId: Numeric identifier assigned at runtime by ProviderManager +using ProviderId = std::uint16_t; + +constexpr ProviderId kInvalidProviderId = std::numeric_limits::max(); + +// Predefined Provider Names (configuration-time identifiers) +const ProviderName kProviderNameOpenSSL{"OPENSSL"}; +const ProviderName kProviderNameSoftHSM{"SOFTHSM"}; + +using OperationActor = uint16_t; +using OperationAction = uint16_t; + +// Starting point for custom actors +inline constexpr OperationActor CUSTOM_ACTOR_START = 1 << (std::numeric_limits::digits - 1); + +struct OperationIdentifier +{ + OperationActor operationActor{0}; + OperationAction operationAction{0}; +}; + +// ============================================================================ +// Non-owning buffer types for zero-copy parameter passing +// ============================================================================ +struct NoParam +{ +}; + +/// Non-owning read-only virtual memory buffer +struct VirtualMemoryBufferConst +{ + const uint8_t* data; + std::size_t size; +}; + +// TODO: Physical contiguous memory buffers (can be derived from these buffer types, once needed) +// But we still want to maintain separate types in terms of constness + +/// Non-owning mutable virtual memory buffer +struct VirtualMemoryBuffer +{ + uint8_t* data; + std::size_t size; +}; + +// ============================================================================ +// Owning buffer types +// ============================================================================ + +/// Owning buffers - These needs to be here to be able to define a common +/// parameter set for both the Lib and Daemon and thus make the std::variants +/// directly compatible. +// /Ideally, we do not make use of these in the daemon +/// to avoid unnecessary copies. +/// On the Lib side, we MUST use these for the responsess +/// since otherwise, we are able to properly control the lifetime of the returned data +/// The IPC shall only return owning types for Lib responses +using OwnedString = std::string; +using OwnedBuffer = std::vector; + +// ============================================================================ +// Unified Parameter Variants +// ============================================================================ + +/// Input parameter variant: always non-owning (borrows from caller or IPC buffer) +using RequestParameter = std::variant; + +/// Output parameter variant: +// - Always owning on the lib side, since we need to take ownership when returning from the IPC +// - Owning and non-owning on daemon side, depending on the useage +// - Requests are non-owning. (IPC owns the data) +// - Responses may be owning, if buffers where created during the operation +using ResponseParameter = std::variant; + +// ============================================================================ +// Unified Operation Request/Response +// ============================================================================ +using RequestParameters = std::vector; +using ResponseParameters = std::vector; + +// Stream operation state for synchronous flow enforcement +enum class StreamOperationState : std::uint8_t +{ + IDLE = 0, // No operation in progress + STREAM_INIT = 1, // INIT operation completed, stream ready + STREAM_ACTIVE = 2, // Stream started and can be updated +}; + +/** + * @brief Functional categories of crypto providers + * Used to categorize providers by their purpose/capability + */ +enum class CryptoProviderType : std::uint8_t +{ + DEFAULT, ///< Default provider for general operations + HARDWARE, ///< Hardware-based crypto provider (TPM, HSM) + SOFTWARE, ///< Software-based crypto provider + SPECIALIZED, ///< Specialized provider for specific operations +}; + +} // namespace score::crypto::daemon::common + +/** + * @brief Hash specialization for CryptoProviderType enum to enable use in + * unordered_map + */ +namespace std +{ +template <> +struct hash +{ + size_t operator()(score::crypto::daemon::common::CryptoProviderType type) const noexcept + { + return std::hash()(static_cast(type)); + } +}; +} // namespace std + +#endif // SCORE_CRYPTO_DAEMON_COMMON_TYPES_HPP diff --git a/score/crypto/daemon/config/BUILD b/score/crypto/daemon/config/BUILD new file mode 100644 index 0000000..f25594f --- /dev/null +++ b/score/crypto/daemon/config/BUILD @@ -0,0 +1,76 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +# Export FlatBuffers schema for use by tests +filegroup( + name = "crypto_config_fbs_schema", + srcs = ["crypto_config.fbs"], + visibility = ["//:__subpackages__"], +) + +genrule( + name = "generated_crypto_config", + srcs = ["crypto_config_fbs_schema"], + outs = [ + "crypto_config_generated.h", + ], + cmd = "$(location @flatbuffers//:flatc) --cpp -o $(@D) $(SRCS)", + tools = ["@flatbuffers//:flatc"], +) + +# Configuration types and structures (headers only) +cc_library( + name = "config_types", + hdrs = ["inc/config.hpp"], + includes = ["inc"], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/daemon/common", + "//score/crypto/daemon/provider/pkcs11:pkcs11_token_config", + "//score/crypto/daemon/provider/score_provider:score_provider_config", + ], +) + +# FlatBuffers configuration parser +cc_library( + name = "flatbuffer_config_parser", + srcs = [ + "src/flatbuffer_config_parser.cpp", + ":generated_crypto_config", + ], + hdrs = [ + "src/flatbuffer_config_parser.hpp", + ":generated_crypto_config", + ], + includes = ["inc"], + visibility = ["//:__subpackages__"], + deps = [ + ":config_types", + "//score/crypto/common:common_types", + "//score/crypto/daemon/common", + "@flatbuffers", + ], +) + +cc_library( + name = "config", + srcs = ["src/config.cpp"], + includes = ["inc"], + visibility = ["//:__subpackages__"], + deps = [ + ":config_types", + ":flatbuffer_config_parser", + "//score/crypto/daemon/common", + ], +) diff --git a/score/crypto/daemon/config/crypto_config.fbs b/score/crypto/daemon/config/crypto_config.fbs new file mode 100644 index 0000000..d123643 --- /dev/null +++ b/score/crypto/daemon/config/crypto_config.fbs @@ -0,0 +1,66 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +// Crypto Configuration Schema +// Currently only includes key slot configuration + +file_identifier "CCFG"; // Crypto Configuration + +namespace score.crypto.daemon.config.keyslot; + +/// Single key slot definition from the configuration source +/// Mirrors KeyConfig::KeySlotEntry structure +table KeySlotEntry { + /// Human-readable resource ID (e.g., "vehicle/hmac-256") + slot_name: string (required); + /// Algorithm string (e.g., "HMAC-SHA256", "AES-256-CMAC") + algorithm: string (required); + /// Ordered provider names. [0] is the primary (sole writer) + provider_names: [string] (required); + /// List of operations allowed for the key in the slot (e.g., "MAC", "ENCRYPT|DECRYPT") + allowed_operations: string (required); + /// UIDs permitted to read/load from the slot + allowed_uids: [uint32] (required); + /// UIDs permitted to write/generate into the slot + allowed_write_uids: [uint32] (required); + /// Absolute path to the deployment descriptor file. + deployment_path: string (required); + /// Format of the deployment descriptor (default "kv"). + deployment_format: string (required); +} + +/// Per-application resource ID to slot name mapping +/// Applications reference keys by portable application-local names +/// (e.g., "signing_key") +table AppResourceEntry { + /// UID of the application that owns this mapping + uid: uint32; + /// Application-local resource name + app_resource_id: string (required); + /// Actual slot name registered in the daemon registry + slot_name: string (required); +} + +/// Key slot config contains all slot and app resource definitions +table KeySlotConfig { + /// All key slot definitions + slot_entries: [KeySlotEntry] (required); + /// All per-app resource mappings + app_resource_entries: [AppResourceEntry] (required); +} + +/// Top-level configuration root_type +table CryptoConfig { + key_slot_config: KeySlotConfig (required); +} + +root_type CryptoConfig; diff --git a/score/crypto/daemon/config/inc/config.hpp b/score/crypto/daemon/config/inc/config.hpp new file mode 100644 index 0000000..0e5caf9 --- /dev/null +++ b/score/crypto/daemon/config/inc/config.hpp @@ -0,0 +1,471 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_CONFIG_CONFIG_HPP +#define SCORE_CRYPTO_DAEMON_CONFIG_CONFIG_HPP + +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_token_config.hpp" +#include "score/crypto/daemon/provider/score_provider/score_provider_config.hpp" + +#include +#include +#include +#include +#include +#include +#include + +// TODO: Need to move the individual configs into their respective components (dependency inversion) +// The config component shall be demanded to fulfill the needs of the components not vice versa. + +namespace score::crypto::daemon::config +{ + +// Configuration environment variable and default paths +/// @brief Environment variable name for specifying the configuration file path +constexpr std::string_view CRYPTO_CONFIG_FILE_ENV = "CRYPTO_CONFIG_FILE"; + +/// @brief Default configuration file paths (in order of preference) +const std::array DEFAULT_CONFIG_PATHS = { + "./etc/crypto_config.bin", +}; + +// TODO: each component should define its own small config header (dependency inversion). +// The config component is obligated to implement all relevant component configurations and e.g. load them from a +// flatbuffer file. +// +// How the config is passed into a constructor is handled via the respective factory methods. Only a factory will +// decide which configuration load strategy will be used and how to acquire the component relevant configuration for +// construction. +// TODO: To use the function later. Only a place holder now. Rationale is to protect against resource exchaustion by +// any application +/// @brief Resource quota policy — daemon-wide or provider-scoped limits on key creation per connection. +/// +/// Owned by daemon configuration, not by individual key slots. +struct ResourceQuotaPolicy +{ + uint32_t max_ephemeral_keys_per_connection{32U}; ///< Per-connection ephemeral key limit + uint32_t max_loaded_keys_per_connection{16U}; ///< Per-connection loaded-from-slot key limit + uint64_t max_total_key_material_bytes{4U * 1024U * 1024U}; ///< Global 4 MiB cap +}; + +/** + * @brief IPC/Communication configuration section + */ +class IPCConfig +{ + public: + IPCConfig() = default; + + uint32_t GetNumOfIPCThreads() const + { + return m_num_ipc_threads; + } + const std::string& GetServerAddress() const + { + return m_server_address; + } + uint16_t GetServerPort() const + { + return m_server_port; + } + + void SetNumOfIPCThreads(uint32_t threads) + { + m_num_ipc_threads = threads; + } + void SetServerAddress(const std::string& address) + { + m_server_address = address; + } + void SetServerPort(uint16_t port) + { + m_server_port = port; + } + + private: + uint32_t m_num_ipc_threads = 4; + std::string m_server_address = "0.0.0.0"; + uint16_t m_server_port = 50051; +}; + +/** + * @brief Provider configuration - contains metadata for adding a new provider + * + * This structure holds the necessary information to create and initialize a + * provider. Providers are identified by their ProviderId and can be categorized + * by CryptoProviderType. The actual provider instance creation is handled by + * the factory implementation, which can be easily extended to support new + * provider types. + */ +struct ProviderConfig +{ + common::ProviderId providerId; ///< Unique provider identifier + common::CryptoProviderType cryptoType; ///< Functional category + bool enabled; ///< Whether this provider should be initialized + ResourceQuotaPolicy quota_policy{}; ///< Per-provider quota policy override + + ProviderConfig() = default; + ProviderConfig(const common::ProviderId& id, common::CryptoProviderType cryptoType, bool enabled = true) + : providerId(id), cryptoType(cryptoType), enabled(enabled) + { + } +}; + +/** + * @brief Provider initialization configuration + * + * This structure defines the complete provider setup for the factory, + * allowing daemon to configure which providers to initialize and set defaults + * before calling Initialize(). + */ +struct ProviderInitConfig +{ + std::vector providers; ///< List of providers to initialize + std::unordered_map + typeToProviderId; ///< Mapping of provider types to their default + ///< ProviderId + + ProviderInitConfig() = default; + + /** + * @brief Add a provider configuration + * @param config Provider configuration to add + */ + void AddProviderConfig(const ProviderConfig& config) + { + providers.push_back(config); + } + + /** + * @brief Set the default provider for a specific crypto provider type + * @param cryptoType The functional category + * @param providerId The provider ID to use for this type + */ + void SetDefaultProviderForType(common::CryptoProviderType cryptoType, const common::ProviderId& providerId) + { + typeToProviderId[cryptoType] = providerId; + } +}; + +/** + * @brief General daemon configuration section + */ +class GeneralConfig +{ + public: + GeneralConfig() = default; + + const std::string& GetLogLevel() const + { + return m_log_level; + } + uint32_t GetMaxSessions() const + { + return m_max_sessions; + } + const ResourceQuotaPolicy& GetQuotaPolicy() const + { + return m_quota_policy; + } + + void SetLogLevel(const std::string& level) + { + m_log_level = level; + } + void SetMaxSessions(uint32_t max) + { + m_max_sessions = max; + } + void SetQuotaPolicy(const ResourceQuotaPolicy& quota_policy) + { + m_quota_policy = quota_policy; + } + + private: + std::string m_log_level = "info"; + uint32_t m_max_sessions = 100; + ResourceQuotaPolicy m_quota_policy{}; +}; + +/** + * @brief Key management configuration section + * + * Stores key slot definitions parsed from the daemon's configuration source + * (JSON manifest or flatbuffer). Each entry describes a persistent key slot: + * its human-readable name, algorithm, owning provider, access policy, and + * a deployment path that points to the external deployment descriptor. + * + * At daemon startup, a ConfigDrivenSlotCatalog reads these entries and calls + * SlotRegistry::RegisterSlot() for each one. + * + * Example JSON slot entry: + * @code + * { + * "slot_name": "vehicle/hmac-256", + * "algorithm": "HMAC-SHA256", + * "provider_names": ["OPENSSL"], + * "allowed_operations": "MAC", + * "access_policy": { "allowed_uids": [0, 1000] }, + * "deployment_path": "/opt/crypto/deploy/vehicle_hmac_256.kv", + * "deployment_format": "kv" + * } + * @endcode + */ +class KeyConfig +{ + public: + /// @brief A single key slot definition from the configuration source. + struct KeySlotEntry + { + std::string slot_name; + std::string algorithm; + /// @brief Ordered list of provider IDs. Index 0 is the primary (sole writer). + /// Secondary providers may read/load the key via cross-provider transfer. + std::vector provider_names; + std::string allowed_operations; ///< String representation (e.g., "MAC", "ENCRYPT|DECRYPT") + std::vector allowed_uids; ///< UIDs permitted to read/load from the slot + std::vector allowed_write_uids; ///< UIDs permitted to write/generate into the slot + /// @brief Absolute path to the deployment descriptor file. + std::string deployment_path; + /// @brief Format of the deployment descriptor (default "kv"). + std::string deployment_format{"kv"}; + }; + + /// @brief Per-application resource ID to slot name mapping. + /// + /// Applications reference keys by portable application-local names + /// (e.g., "signing_key") rather than hardcoded daemon slot names + /// (e.g., "vehicle/rsa-2048"). Each application delivers a small set of + /// entries that map its local names to actual daemon slot names. + /// + /// The daemon's SlotRegistry stores these mappings and resolves them + /// transparently during ResolveResource IPC calls. + struct AppResourceEntry + { + uint32_t uid; ///< UID of the application that owns this mapping + std::string app_resource_id; ///< Application-local resource name + std::string slot_name; ///< Actual slot name registered in the daemon registry + }; + + KeyConfig() = default; + + /// @brief Add a key slot definition (called by parser). + void AddSlotEntry(KeySlotEntry entry) + { + m_slot_entries.push_back(std::move(entry)); + } + + /// @brief Get all parsed key slot definitions. + const std::vector& GetSlotEntries() const + { + return m_slot_entries; + } + + /// @brief Add an application resource mapping entry (called by parser). + void AddAppResourceEntry(AppResourceEntry entry) + { + m_app_resource_entries.push_back(std::move(entry)); + } + + /// @brief Get all per-application resource ID mappings. + const std::vector& GetAppResourceEntries() const + { + return m_app_resource_entries; + } + + /// @brief Path to the JSON key slot manifest file (optional). + /// + /// If non-empty, ConfigDrivenSlotCatalog reads this file during Load(). + /// If empty, only the entries added via AddSlotEntry() are used. + void SetManifestPath(const std::string& path) + { + m_manifest_path = path; + } + + const std::string& GetManifestPath() const + { + return m_manifest_path; + } + + private: + std::vector m_slot_entries; + std::vector m_app_resource_entries; + std::string m_manifest_path; +}; + +/// @brief Type alias: PKCS#11 config is owned by the pkcs11 provider subsystem. +/// +/// config.hpp exposes it under the config namespace for backward-compatible use; +/// the full definition lives in pkcs11_token_config.hpp. +using Pkcs11Config = ::score::crypto::daemon::provider::pkcs11::Pkcs11Config; + +/// @brief Type alias: Score-provider config is owned by the score_provider subsystem. +/// +/// config.hpp exposes it under the config namespace for consistent access; +/// the full definition lives in score_provider_config.hpp. +using ScoreProviderConfig = ::score::crypto::daemon::provider::score_provider::ScoreProviderConfig; + +/** + * @brief Certificate management configuration section (placeholder for future) + */ +class CertificateConfig +{ + public: + CertificateConfig() = default; + // Add methods as needed +}; + +/** + * @brief Top-level configuration class + * + * Aggregates all configuration sections and provides access to them. + * Components receive const reference to Config and access sub-sections via getter methods. + * + * Example usage: + * Config config; + * config.ParseEnvironment(); + * config.ParseCommandLine(argc, argv); + * + * // Components use it: + * void SomeComponent::Initialize(const Config& config) { + * auto threads = config.GetIPCConfig().GetNumOfIPCThreads(); + * } + */ +class Config +{ + public: + Config() = default; + ~Config() = default; + + // Delete copy/move - single instance managed by main() + Config(const Config&) = delete; + Config& operator=(const Config&) = delete; + Config(Config&&) = delete; + Config& operator=(Config&&) = delete; + + /** + * @brief Parse configuration from environment variables + * @param env Optional environment map for testing. If empty, reads from actual environment. + * @return true if parsing succeeded, false on error + */ + bool ParseEnvironment(const std::map& env = {}); + + /** + * @brief Parse configuration from command line arguments + * @param argc Argument count + * @param argv Argument values + * @return true if parsing succeeded, false on error + */ + bool ParseCommandLine(int argc, char** argv); + + /** + * @brief Parse configuration (like flatbuffer) + * @param none + * @return true if parsing succeeded, false on error + */ + bool ParseConfig(); + + /** + * @brief Validate all configuration sections + * @return true if all sections are valid + */ + bool Validate() const; + + // Const access for components (read-only) + const IPCConfig& GetIPCConfig() const + { + return m_ipc; + } + const ProviderConfig& GetProviderConfig() const + { + return m_provider; + } + const ProviderInitConfig& GetProviderInitConfig() const + { + return m_provider_init; + } + const GeneralConfig& GetGeneralConfig() const + { + return m_general; + } + const KeyConfig& GetKeyConfig() const + { + return m_key; + } + const CertificateConfig& GetCertificateConfig() const + { + return m_certificate; + } + const Pkcs11Config& GetPkcs11Config() const + { + return m_pkcs11; + } + const ScoreProviderConfig& GetScoreProviderConfig() const + { + return m_score_provider; + } + + // Non-const access for main()/parsers (can modify) + IPCConfig& GetIPCConfig() + { + return m_ipc; + } + ProviderConfig& GetProviderConfig() + { + return m_provider; + } + GeneralConfig& GetGeneralConfig() + { + return m_general; + } + KeyConfig& GetKeyConfig() + { + return m_key; + } + CertificateConfig& GetCertificateConfig() + { + return m_certificate; + } + Pkcs11Config& GetPkcs11Config() + { + return m_pkcs11; + } + ScoreProviderConfig& GetScoreProviderConfig() + { + return m_score_provider; + } + + private: + IPCConfig m_ipc; + ProviderConfig m_provider; + ProviderInitConfig m_provider_init; + GeneralConfig m_general; + KeyConfig m_key; + CertificateConfig m_certificate; + Pkcs11Config m_pkcs11; + ScoreProviderConfig m_score_provider; + + // Helper methods + std::string GetEnvVar(const char* name, const std::map& env) const; + bool ParseStringArg(const std::string& arg, + const std::string& value, + const std::string& option, + std::string& target); + bool ParseUint32Arg(const std::string& arg, const std::string& value, const std::string& option, uint32_t& target); + bool ParseUint16Arg(const std::string& arg, const std::string& value, const std::string& option, uint16_t& target); +}; + +} // namespace score::crypto::daemon::config + +#endif // SCORE_CRYPTO_DAEMON_CONFIG_CONFIG_HPP diff --git a/score/crypto/daemon/config/src/config.cpp b/score/crypto/daemon/config/src/config.cpp new file mode 100644 index 0000000..a93102f --- /dev/null +++ b/score/crypto/daemon/config/src/config.cpp @@ -0,0 +1,133 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/config/inc/config.hpp" +#include "score/crypto/daemon/config/src/flatbuffer_config_parser.hpp" + +#include +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::config +{ + +std::string Config::GetEnvVar(const char* name, const std::map& env) const +{ + // If env map is provided (testing), use it + if (!env.empty()) + { + auto it = env.find(name); + return (it != env.end()) ? it->second : ""; + } + // Otherwise, read from actual environment + const char* value = std::getenv(name); + return value ? value : ""; +} + +bool Config::ParseEnvironment(const std::map& env) +{ + // TODO: Implement environment variable parsing + // Example: + // std::string threads = GetEnvVar("CRYPTO_IPC_THREADS", env); + // if (!threads.empty()) { + // m_ipc.SetNumOfIPCThreads(std::stoul(threads)); + // } + return true; +} + +bool Config::ParseStringArg(const std::string& arg, + const std::string& value, + const std::string& option, + std::string& target) +{ + // TODO: Implement string argument parsing + return false; +} + +bool Config::ParseUint32Arg(const std::string& arg, + const std::string& value, + const std::string& option, + uint32_t& target) +{ + // TODO: Implement uint32 argument parsing + return false; +} + +bool Config::ParseUint16Arg(const std::string& arg, + const std::string& value, + const std::string& option, + uint16_t& target) +{ + // TODO: Implement uint16 argument parsing + return false; +} + +bool Config::ParseCommandLine(int argc, char** argv) +{ + // TODO: Implement command line parsing + return true; +} + +bool Config::ParseConfig() +{ + auto config_file_path = std::getenv(CRYPTO_CONFIG_FILE_ENV.data()); + if (config_file_path) + { + if (!std::filesystem::exists(config_file_path)) + { + std::cerr << "[CONFIG] Configuration file does not exist: " << config_file_path << "\n"; + return false; + } + std::cout << "[CONFIG] Parsing configuration from: " << config_file_path << "\n"; + auto result = FlatBufferConfigParser::ParseFromFile(config_file_path, m_key); + if (!result.has_value()) + { + std::cerr << "[CONFIG] Failed to parse FlatBuffers configuration file: " << config_file_path << "\n"; + return false; + } + return true; + } + + // Try default paths (in order of preference) + for (const auto& path : DEFAULT_CONFIG_PATHS) + { + if (std::filesystem::exists(path)) + { + std::cout << "[CONFIG] Found configuration at default path: " << path << "\n"; + auto result = FlatBufferConfigParser::ParseFromFile(path, m_key); + if (!result.has_value()) + { + std::cerr << "[CONFIG] Failed to parse configuration from: " << path << "\n"; + return false; + } + return true; + } + } + + std::cerr << "[CONFIG] ERROR: No configuration file found in default paths:\n"; + for (const auto& path : DEFAULT_CONFIG_PATHS) + { + std::cerr << " - " << path << "\n"; + } + return false; +} + +bool Config::Validate() const +{ + // TODO: Implement validation + return true; +} + +} // namespace score::crypto::daemon::config diff --git a/score/crypto/daemon/config/src/flatbuffer_config_parser.cpp b/score/crypto/daemon/config/src/flatbuffer_config_parser.cpp new file mode 100644 index 0000000..8bcb948 --- /dev/null +++ b/score/crypto/daemon/config/src/flatbuffer_config_parser.cpp @@ -0,0 +1,287 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include +#include +#include +#include +#include + +#include "score/crypto/daemon/config/src/flatbuffer_config_parser.hpp" + +// Include FlatBuffers generated file +#include "score/crypto/daemon/config/crypto_config_generated.h" + +using namespace score::crypto::daemon::config::keyslot; + +namespace score::crypto::daemon::config +{ + +Expected FlatBufferConfigParser::ValidateBuffer(const uint8_t* data, + size_t size) +{ + if (!data || size < kMinBufferSize) + { + std::cerr << LOG_PREFIX << "Buffer too small or null pointer. Required: >=4 bytes, Got: " << size << "\n"; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + if (!CryptoConfigBufferHasIdentifier(data)) + { + std::cerr << LOG_PREFIX << "Buffer does not have expected file identifier\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + + return std::monostate{}; +} + +Expected FlatBufferConfigParser::ParseKeySlotConfig(const CryptoConfig* root, + KeyConfig& out_config) +{ + if (!root) + { + std::cerr << LOG_PREFIX << "Failed to get FlatBuffers root object\n"; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + // Get key slot config from root + const auto* key_slot_config = root->key_slot_config(); + if (!key_slot_config) + { + std::cerr << LOG_PREFIX << "Failed to get key_slot_config from root object\n"; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + // Parse slot entries + auto slot_entries_result = ParseSlotEntries(key_slot_config, out_config); + if (!slot_entries_result.has_value()) + { + return make_unexpected(slot_entries_result.error()); + } + + // Parse app resource entries + auto app_resource_result = ParseAppResourceEntries(key_slot_config, out_config); + if (!app_resource_result.has_value()) + { + return make_unexpected(app_resource_result.error()); + } + + std::cout << LOG_PREFIX << "Successfully parsed configuration. Loaded " << out_config.GetSlotEntries().size() + << " slot(s) and " << out_config.GetAppResourceEntries().size() << " app resource mapping(s).\n"; + + return std::monostate{}; +} + +Expected FlatBufferConfigParser::ParseSlotEntries( + const KeySlotConfig* key_slot_config, + KeyConfig& out_config) +{ + const auto* slot_entries = key_slot_config->slot_entries(); + if (!slot_entries) + { + return std::monostate{}; // No slot entries is not an error + } + + for (const auto* entry : *slot_entries) + { + if (!entry) + { + std::cerr << LOG_PREFIX << "Null slot entry encountered - invalid configuration\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + + KeyConfig::KeySlotEntry slot_entry; + + if (!entry->slot_name()) + { + std::cerr << LOG_PREFIX << "Slot entry missing required field 'slot_name'\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + slot_entry.slot_name = entry->slot_name()->str(); + + if (!entry->algorithm()) + { + std::cerr << LOG_PREFIX << "Slot entry missing required field 'algorithm'\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + slot_entry.algorithm = entry->algorithm()->str(); + + if (!entry->provider_names()) + { + std::cerr << LOG_PREFIX << "Slot entry missing required field 'provider_names'\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + for (const auto* provider : *entry->provider_names()) + { + if (provider) + { + slot_entry.provider_names.push_back(provider->str()); + } + } + + if (!entry->allowed_operations()) + { + std::cerr << LOG_PREFIX << "Slot entry missing required field 'allowed_operations'\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + slot_entry.allowed_operations = entry->allowed_operations()->str(); + + if (!entry->allowed_uids()) + { + std::cerr << LOG_PREFIX << "Slot entry missing required field 'allowed_uids'\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + for (auto uid : *entry->allowed_uids()) + { + slot_entry.allowed_uids.push_back(uid); + } + + if (!entry->allowed_write_uids()) + { + std::cerr << LOG_PREFIX << "Slot entry missing required field 'allowed_write_uids'\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + for (auto uid : *entry->allowed_write_uids()) + { + slot_entry.allowed_write_uids.push_back(uid); + } + + if (!entry->deployment_path()) + { + std::cerr << LOG_PREFIX << "Slot entry missing required field 'deployment_path'\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + slot_entry.deployment_path = entry->deployment_path()->str(); + + if (!entry->deployment_format()) + { + std::cerr << LOG_PREFIX << "Slot entry missing required field 'deployment_format'\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + slot_entry.deployment_format = entry->deployment_format()->str(); + + out_config.AddSlotEntry(std::move(slot_entry)); + + std::cout << LOG_PREFIX << "Loaded slot: '" << out_config.GetSlotEntries().back().slot_name << "'\n"; + } + + return std::monostate{}; +} + +Expected FlatBufferConfigParser::ParseFromFile(std::string_view filepath, + KeyConfig& out_config) +{ + std::ifstream file(std::string(filepath), std::ios::binary | std::ios::ate); + if (!file.is_open()) + { + std::cerr << LOG_PREFIX << "Failed to open file: " << filepath << "\n"; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + // Get file size + std::streamsize size = file.tellg(); + if (size <= 0) + { + std::cerr << LOG_PREFIX << "File is empty: " << filepath << "\n"; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + file.seekg(0, std::ios::beg); + + // Read entire file into buffer + if (size > std::numeric_limits::max()) + { + std::cerr << LOG_PREFIX << "File size exceeds maximum allowed size: " << filepath << "\n"; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + std::vector buffer(static_cast(size)); + if (!file.read(reinterpret_cast(buffer.data()), size)) + { + std::cerr << LOG_PREFIX << "Failed to read file: " << filepath << "\n"; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + return ParseFromBuffer(buffer.data(), buffer.size(), out_config); +} + +Expected FlatBufferConfigParser::ParseAppResourceEntries( + const KeySlotConfig* key_slot_config, + KeyConfig& out_config) +{ + const auto* app_resource_entries = key_slot_config->app_resource_entries(); + if (!app_resource_entries) + { + return std::monostate{}; // No app resource entries is not an error + } + + for (const auto* entry : *app_resource_entries) + { + if (!entry) + { + std::cerr << LOG_PREFIX << "Null app resource entry encountered - invalid configuration\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + + KeyConfig::AppResourceEntry resource_entry; + resource_entry.uid = entry->uid(); + + // All fields below are required per schema - add defensive checks + if (!entry->app_resource_id()) + { + std::cerr << LOG_PREFIX << "App resource entry missing required field 'app_resource_id'\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + resource_entry.app_resource_id = entry->app_resource_id()->str(); + + if (!entry->slot_name()) + { + std::cerr << LOG_PREFIX << "App resource entry missing required field 'slot_name'\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + resource_entry.slot_name = entry->slot_name()->str(); + + out_config.AddAppResourceEntry(std::move(resource_entry)); + } + + return std::monostate{}; +} + +Expected FlatBufferConfigParser::ParseFromBuffer(const uint8_t* data, + size_t size, + KeyConfig& out_config) +{ + if (!data || size == 0) + { + std::cerr << LOG_PREFIX << "Invalid buffer: null pointer or zero size\n"; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + auto validateRes = ValidateBuffer(data, size); + if (!validateRes.has_value()) + { + std::cerr << LOG_PREFIX << "Buffer validation failed\n"; + return make_unexpected(validateRes.error()); + } + + // Get root FlatBuffers object (CryptoConfig is the root_type) + const auto* root = flatbuffers::GetRoot(data); + if (!root) + { + std::cerr << LOG_PREFIX << "Failed to get FlatBuffers root object from buffer\n"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + + return ParseKeySlotConfig(root, out_config); +} + +} // namespace score::crypto::daemon::config diff --git a/score/crypto/daemon/config/src/flatbuffer_config_parser.hpp b/score/crypto/daemon/config/src/flatbuffer_config_parser.hpp new file mode 100644 index 0000000..e709365 --- /dev/null +++ b/score/crypto/daemon/config/src/flatbuffer_config_parser.hpp @@ -0,0 +1,143 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_CONFIG_FLATBUFFER_CONFIG_PARSER_HPP +#define SCORE_CRYPTO_DAEMON_CONFIG_FLATBUFFER_CONFIG_PARSER_HPP + +#include +#include +#include +#include + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/config/inc/config.hpp" + +namespace score::crypto::daemon::config::keyslot +{ +// Forward declarations for FlatBuffers-generated types +class CryptoConfig; +class KeySlotConfig; +} // namespace score::crypto::daemon::config::keyslot + +namespace score::crypto::daemon::config +{ + +/// @brief Parser for FlatBuffers-based configuration files. +/// +/// Transforms serialized KeySlotConfigFB (from score::crypto::daemon::config::keyslot +/// namespace via FlatBuffers) into KeyConfig objects that the rest of the daemon +/// recognizes. +class FlatBufferConfigParser +{ + public: + FlatBufferConfigParser() = default; + ~FlatBufferConfigParser() = default; + + // Delete copy/move — static utility interface only + FlatBufferConfigParser(const FlatBufferConfigParser&) = delete; + FlatBufferConfigParser& operator=(const FlatBufferConfigParser&) = delete; + FlatBufferConfigParser(FlatBufferConfigParser&&) = delete; + FlatBufferConfigParser& operator=(FlatBufferConfigParser&&) = delete; + + /// @brief Parse FlatBuffers key slot configuration from a file. + /// + /// Reads the binary file, validates the FlatBuffers structure, and + /// populates the output KeyConfig with deserialized slot entries and + /// app resource mappings. + /// + /// @param filepath Path to the .fbs binary config file + /// @param out_config Output KeyConfig object to populate + /// @return Success (monostate) on successful parsing, DaemonErrorCode on failure + /// @retval kInvalidArgument File not found, file empty, or buffer too small + /// @retval kInternal Invalid FlatBuffers structure or missing required fields + /// + /// @note If parsing fails, out_config state is undefined; don't use it. + static Expected ParseFromFile(std::string_view filepath, + KeyConfig& out_config); + + /// @brief Parse FlatBuffers key slot configuration from a memory buffer. + /// + /// Validates the FlatBuffers structure in memory and populates the output + /// KeyConfig with deserialized entries. + /// + /// @param data Pointer to buffer containing serialized FlatBuffers data + /// @param size Size of data buffer in bytes + /// @param out_config Output KeyConfig object to populate + /// @return Success (monostate) on successful parsing, DaemonErrorCode on failure + /// @retval kInvalidArgument Null pointer, zero size, or invalid FlatBuffers root + /// @retval kInternal Missing required fields or invalid file identifier + /// + /// @note If parsing fails, out_config state is undefined; don't use it. + static Expected ParseFromBuffer(const uint8_t* data, + size_t size, + KeyConfig& out_config); + + private: + static constexpr std::string_view LOG_PREFIX = "[FLATBUFFER_PARSER] "; + static constexpr size_t kMinBufferSize = 4; // Minimum size to contain FlatBuffers file identifier + + /// @brief Validate FlatBuffers root and file identifier. + /// + /// Ensures the buffer contains a valid KeySlotConfigFB root with + /// the expected file identifier "CCFG" (from score::crypto::daemon::config::keyslot). + /// + /// @param data Pointer to buffer + /// @param size Buffer size in bytes + /// @return Success (monostate) if valid, DaemonErrorCode on validation failure + /// @retval kInvalidArgument Null pointer, buffer too small, or invalid identifier + /// @retval kInternal Invalid FlatBuffers structure + static Expected ValidateBuffer(const uint8_t* data, size_t size); + + /// @brief Parse the key slot configuration from the FlatBuffers root object. + /// + /// Extracts the KeySlotConfig from the CryptoConfig root and delegates + /// parsing of slot entries and app resource entries to helper methods. + /// + /// @param root Pointer to the CryptoConfig FlatBuffers root object + /// @param out_config Output KeyConfig object to populate + /// @return Success (monostate) on successful parsing, DaemonErrorCode on failure + /// @retval kInvalidArgument Null root or missing key_slot_config + /// @retval kInternalError Parsing of entries failed + static Expected ParseKeySlotConfig(const keyslot::CryptoConfig* root, + KeyConfig& out_config); + + /// @brief Parse slot entries from key slot configuration. + /// + /// Iterates through slot entries in the KeySlotConfig and populates + /// KeyConfig with parsed KeySlotEntry objects. + /// + /// @param key_slot_config Pointer to the KeySlotConfig FlatBuffers object + /// @param out_config Output KeyConfig object to populate + /// @return Success (monostate) on successful parsing, DaemonErrorCode on failure + /// @retval kInternalError Invalid entry structure or missing required fields + static Expected ParseSlotEntries( + const keyslot::KeySlotConfig* key_slot_config, + KeyConfig& out_config); + + /// @brief Parse app resource entries from key slot configuration. + /// + /// Iterates through app resource entries in the KeySlotConfig and populates + /// KeyConfig with parsed AppResourceEntry objects. + /// + /// @param key_slot_config Pointer to the KeySlotConfig FlatBuffers object + /// @param out_config Output KeyConfig object to populate + /// @return Success (monostate) on successful parsing, DaemonErrorCode on failure + /// @retval kInternalError Invalid entry structure or missing required fields + static Expected ParseAppResourceEntries( + const keyslot::KeySlotConfig* key_slot_config, + KeyConfig& out_config); +}; + +} // namespace score::crypto::daemon::config + +#endif // SCORE_CRYPTO_DAEMON_CONFIG_FLATBUFFER_CONFIG_PARSER_HPP diff --git a/score/crypto/daemon/control_plane/BUILD b/score/crypto/daemon/control_plane/BUILD new file mode 100644 index 0000000..c2731c5 --- /dev/null +++ b/score/crypto/daemon/control_plane/BUILD @@ -0,0 +1,62 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "control_operations", + hdrs = ["control_operations.h"], + visibility = [ + "//:__subpackages__", + ], + deps = ["//score/crypto/daemon/common"], +) + +cc_library( + name = "request_handler_hdr", + hdrs = [ + "control_protocol.h", + "i_request_handler.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/daemon/data_manager", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "control_plane", + srcs = [ + "src/basic_handler_chain_factory.cpp", + "src/connection_handler.cpp", + "src/connection_handler.hpp", + ], + hdrs = [ + "basic_handler_chain_factory.hpp", + "control_protocol.h", + "i_control_server.h", + "i_handler_chain_factory.hpp", + "i_request_handler.hpp", + ], + linkstatic = True, # Internal library should only be statically linked + visibility = ["//:__subpackages__"], + deps = [ + ":control_operations", + ":request_handler_hdr", + "//score/crypto/daemon/config", + "//score/crypto/daemon/data_manager", + "//score/crypto/daemon/mediator", + "//score/crypto/daemon/provider:provider_manager", + "@score_baselibs//score/result", + ], +) diff --git a/score/crypto/daemon/control_plane/basic_handler_chain_factory.hpp b/score/crypto/daemon/control_plane/basic_handler_chain_factory.hpp new file mode 100644 index 0000000..69206b0 --- /dev/null +++ b/score/crypto/daemon/control_plane/basic_handler_chain_factory.hpp @@ -0,0 +1,76 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_CONTROL_PLANE_REQUEST_HANDLER_FACTORY_IMPL_HPP_ +#define SCORE_CRYPTO_DAEMON_CONTROL_PLANE_REQUEST_HANDLER_FACTORY_IMPL_HPP_ + +#include "score/crypto/daemon/config/inc/config.hpp" +#include "score/crypto/daemon/control_plane/i_handler_chain_factory.hpp" +#include "score/crypto/daemon/control_plane/i_request_handler.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" +#include "score/crypto/daemon/key_management/core/key_management_service.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" +#include + +namespace score::crypto::daemon::control_plane +{ + +/** + * @class BasicHandlerChainFactory + * @brief Concrete factory implementation for creating request handler chains. + * + * Shares thread-safe components: IDataManager, ProviderManager + */ +class BasicHandlerChainFactory : public IHandlerChainFactory +{ + public: + /** + * @brief Constructs factory with shared thread-safe dependencies. + * + * @param data_manager Thread-safe shared data manager (shared across threads) + * @param provider_manager Shared provider manager (shared across threads) + * @param config Configuration reference (must outlive factory) + */ + BasicHandlerChainFactory(std::shared_ptr data_manager, + std::shared_ptr provider_manager, + const config::Config& config, + key_management::KeyManagementService::Sptr km_service = nullptr); + + // Non-copyable - factories should not be duplicated + BasicHandlerChainFactory(const BasicHandlerChainFactory&) = delete; + BasicHandlerChainFactory& operator=(const BasicHandlerChainFactory&) = delete; + + // Movable - factories can be transferred (unique_ptr ownership transfer) + + /** + * @brief Creates a new request handler wrapper. + * + * Each call creates NEW ConnectionHandler wrapping SHARED Mediator. + * The shared mediator preserves context state across requests from different threads. + * + * @return std::unique_ptr New handler wrapper + * + * @par Thread-safety Requirements + * Implementation is thread safe : The current implementation has no shared mutable state. + */ + std::unique_ptr CreateRequestHandler() override; + + private: + std::shared_ptr m_data_manager; // Shared across threads + std::shared_ptr m_provider_manager; // Shared across threads + const config::Config& m_config; + key_management::KeyManagementService::Sptr m_km_service; // Shared across threads +}; + +} // namespace score::crypto::daemon::control_plane + +#endif // SCORE_CRYPTO_DAEMON_CONTROL_PLANE_REQUEST_HANDLER_FACTORY_IMPL_HPP_ diff --git a/score/crypto/daemon/control_plane/control_operations.h b/score/crypto/daemon/control_plane/control_operations.h new file mode 100644 index 0000000..7ce25c3 --- /dev/null +++ b/score/crypto/daemon/control_plane/control_operations.h @@ -0,0 +1,65 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_CONTROL_PLANE_CONTROL_OPERATIONS_H +#define SCORE_CRYPTO_DAEMON_CONTROL_PLANE_CONTROL_OPERATIONS_H + +#include "control_protocol.h" +#include "score/crypto/daemon/common/actors.hpp" +#include "score/crypto/daemon/common/types.hpp" + +#include + +namespace score::crypto::daemon::control_plane::operations +{ + +using OperationAction = common::OperationAction; + +// ============================================================================ +// Common Control Plane Operations +// ============================================================================ + +// CONNECTION_OPEN +// Request: data_node_id = 0 (no parent), +// no operation parameters +// Response: status_code (SUCCESS/error) +// uint64_t — daemon-assigned connection_id (root DataNodeId) +// Effect: Creates a root DataNode, initializes connection context +inline constexpr OperationAction CONNECTION_OPEN = 1; + +// CONNECTION_CLOSE +// Request: data_node_id = connection_id, +// no operation parameters +// Response: status_code (SUCCESS) +// no output parameters +// Effect: Deletes the connection node and cascade-deletes all child context nodes +inline constexpr OperationAction CONNECTION_CLOSE = 2; + +// Starting point for custom OPs +inline constexpr OperationAction CUSTOM_OP_START = 1 << (std::numeric_limits::digits - 1); + +// Helpers +inline protocol::OperationIdentifier OpenConnection() +{ + return protocol::OperationIdentifier{.operationActor = common::actors::OP_ACTOR_CONTROL, + .operationAction = operations::CONNECTION_OPEN}; +} + +inline protocol::OperationIdentifier CloseConnection() +{ + return protocol::OperationIdentifier{.operationActor = common::actors::OP_ACTOR_CONTROL, + .operationAction = operations::CONNECTION_CLOSE}; +} + +} // namespace score::crypto::daemon::control_plane::operations + +#endif // SCORE_CRYPTO_DAEMON_CONTROL_PLANE_CONTROL_OPERATIONS_H diff --git a/score/crypto/daemon/control_plane/control_protocol.h b/score/crypto/daemon/control_plane/control_protocol.h new file mode 100644 index 0000000..4cb9b00 --- /dev/null +++ b/score/crypto/daemon/control_plane/control_protocol.h @@ -0,0 +1,865 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_CONTROL_PLANE_CONTROL_PROTOCOL_H +#define SCORE_CRYPTO_DAEMON_CONTROL_PLANE_CONTROL_PROTOCOL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" +#include "score/mw/crypto/api/common/error_domain.hpp" + +namespace score::crypto::daemon::control_plane::protocol +{ + +// ============================================================================ +// Protocol Types +// ============================================================================ +// TODO(error-unification phase-3): OperationResult now uses CryptoErrorCode's underlying type, +// so IPC wire values are 0x01CCxxxx (API-defined ranges), not the old 0x000x daemon ranges. +// If cross-version wire stability ever becomes a requirement (rolling daemon/client upgrades), +// introduce a dedicated serialization enum here and translate at the boundary so CryptoErrorCode +// can evolve independently of the on-wire representation. +using OperationResult = std::underlying_type::type; +static constexpr OperationResult OPERATION_RESULT_SUCCESS = 0; + +// ============================================================================ +// Convenience type aliased to bring them to into the namespace +// ============================================================================ + +using OperationIdentifier = common::OperationIdentifier; +using NoParam = daemon::common::NoParam; +using DataNodeId = daemon::data_manager::DataNodeId; +using OwnedString = common::OwnedString; +using OwnedBuffer = common::OwnedBuffer; +using DataBufferReturn = common::OwnedBuffer; + +// ============================================================================ +// Request and Response Classes +// ============================================================================ + +struct SingleOperationRequest +{ + OperationIdentifier operationId; + common::RequestParameters parameters; + + template + Expected getParameter(std::size_t idx) const + { + if (idx >= parameters.size()) + { + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + } + + if (!std::holds_alternative(parameters[idx])) + { + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + } + + return std::get(parameters[idx]); + } +}; + +struct SingleOperationResponse +{ + OperationIdentifier operationId; + OperationResult result{static_cast(score::mw::crypto::CryptoErrorCode::kInternalError)}; + common::ResponseParameters parameters; + + template + Expected getParameter(std::size_t idx) const + { + if (idx >= parameters.size()) + { + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + } + + if (!std::holds_alternative(parameters[idx])) + { + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + } + + return std::get(parameters[idx]); + } +}; + +// ============================================================================ +// Batching of multiple Operation per Request & Response +// ============================================================================ + +struct OperationRequest +{ + std::vector operations; +}; + +struct OperationResponse +{ + std::vector operations; +}; + +// ============================================================================ +// ControlRequest & ControlResponse +// ============================================================================ + +using RequestId = std::uint64_t; + +/// Request for control operation +struct ControlRequest +{ + RequestId request_id; + // TODO: This is not really nice, since we duplicate the union definition + // But we avoid confusion about the order / portability + // BUT, we should get rid of this in general + // There are currently THREE copies either change none or all + union + { + data_manager::ClientId client_id; + struct + { + uint32_t pid; + uint32_t uid; + }; + }; + union + { + data_manager::DataNodeId data_node_id; + // TODO: Unclear if this depends on endianess or other factors + // Is there a clear statemenet about the order of these elements in the C / C++ spec? + // It may be better to define a named struct within data_manager + struct + { + uint64_t node_id_value : data_manager::DATA_NODE_ID_VALUE_BITS; + uint64_t node_tag_value : data_manager::DATA_NODE_ID_RESERVED_BITS; + }; + }; + protocol::OperationRequest operation; +}; + +static_assert(sizeof(pid_t) == sizeof(decltype(ControlRequest::pid)), + "Error: The size of pid_t is not the expected size."); +static_assert(sizeof(uid_t) == sizeof(decltype(ControlRequest::uid)), + "Error: The size of uid_t is not the expected size."); +static_assert(sizeof(data_manager::ClientId) == + sizeof(decltype(ControlRequest::pid)) + sizeof(decltype(ControlRequest::uid)), + "Error: The size of data_manager::ClientId and its components do not match."); + +/// Response from control operation +struct ControlResponse +{ + RequestId request_id; + protocol::OperationResponse operation; +}; + +/// Extract the process ID (pid) from a ClientId +inline uint32_t GetPidFromClientId(data_manager::ClientId client_id) +{ + // TODO: This is not really nice, since we duplicate the union definition + // But we avoid confusion about the order / portability + // BUT, we should get rid of this in general + // There are currently THREE copies either change none or all + union + { + data_manager::ClientId id; + struct + { + uint32_t pid; + uint32_t uid; + }; + } decomposed{client_id}; + return decomposed.pid; +} + +/// Extract the user ID (uid) from a ClientId +inline uint32_t GetUidFromClientId(data_manager::ClientId client_id) +{ + // TODO: This is not really nice, since we duplicate the union definition + // But we avoid confusion about the order / portability + // BUT, we should get rid of this in general + // There are currently THREE copies either change none or all + union + { + data_manager::ClientId id; + struct + { + uint32_t pid; + uint32_t uid; + }; + } decomposed{client_id}; + return decomposed.uid; +} + +// ============================================================================ +// Builders +// ============================================================================ + +class OperationRequestBuilder +{ + public: + OperationRequestBuilder& operation(const OperationIdentifier& opId) + { + operationRequest.operations.push_back(SingleOperationRequest{opId, {}}); + + return *this; + } + + OperationRequestBuilder& with_no_param() + { + if (validateOperationExists()) + { + operationRequest.operations.back().parameters.push_back(NoParam{}); + } + return *this; + }; + + OperationRequestBuilder& with_in_string(std::string_view string) + { + if (validateOperationExists()) + { + operationRequest.operations.back().parameters.push_back(string); + } + return *this; + }; + + OperationRequestBuilder& with_in_data_buffer(span data) + { + if (validateOperationExists()) + { + operationRequest.operations.back().parameters.push_back( + common::VirtualMemoryBufferConst{data.data(), data.size()}); + } + return *this; + }; + + OperationRequestBuilder& with_in_val_bool(bool val) + { + if (validateOperationExists()) + { + operationRequest.operations.back().parameters.push_back(val); + } + return *this; + }; + + OperationRequestBuilder& with_in_val_uint8(uint8_t val) + { + if (validateOperationExists()) + { + operationRequest.operations.back().parameters.push_back(val); + } + return *this; + }; + + OperationRequestBuilder& with_in_val_uint16(uint16_t val) + { + if (validateOperationExists()) + { + operationRequest.operations.back().parameters.push_back(val); + } + return *this; + }; + + OperationRequestBuilder& with_in_val_uint32(uint32_t val) + { + if (validateOperationExists()) + { + operationRequest.operations.back().parameters.push_back(val); + } + return *this; + }; + + OperationRequestBuilder& with_in_val_uint64(uint64_t val) + { + if (validateOperationExists()) + { + operationRequest.operations.back().parameters.push_back(val); + } + return *this; + }; + + Expected build() + { + if (error) + { + std::cout << "[CONTROL_OP_REQ_BUILDER] ERROR - Cannot build OperationRequest due to previous errors in " + "building process" + << "\n"; + + error = false; + operationRequest.operations.clear(); + + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInternalError); + } + + OperationRequest ret{ + std::move(operationRequest.operations), + }; + return ret; + } + + private: + bool error = false; + + OperationRequest operationRequest; + + bool validateOperationExists() + { + if (operationRequest.operations.empty()) + { + std::cout << "[CONTROL_OP_REQ_BUILDER] ERROR - Trying to add parameter without an operation" + << "\n"; + error = true; + return false; + } + return true; + } +}; + +class OperationResponseBuilder +{ + public: + OperationResponseBuilder& operation(const OperationIdentifier& opId) + { + operationResponse.operations.push_back(SingleOperationResponse{opId, {}}); + + return *this; + } + + OperationResponseBuilder& return_success() + { + if (validateOperationExists()) + { + operationResponse.operations.back().result = OPERATION_RESULT_SUCCESS; + } + return *this; + }; + + OperationResponseBuilder& return_error(score::mw::crypto::CryptoErrorCode error) + { + if (validateOperationExists()) + { + operationResponse.operations.back().result = static_cast(error); + } + return *this; + }; + + /// @brief Daemon-internal overload: translates DaemonErrorCode to the wire + /// CryptoErrorCode value at the IPC boundary. Daemon code should prefer this + /// overload so handler/executor files never reference CryptoErrorCode directly. + OperationResponseBuilder& return_error(score::crypto::daemon::common::DaemonErrorCode error) + { + return return_error(score::crypto::daemon::common::ToCryptoErrorCode(error)); + }; + + OperationResponseBuilder& return_value_bool(bool val) + { + if (validateOperationExists()) + { + operationResponse.operations.back().parameters.push_back(val); + } + return *this; + }; + + OperationResponseBuilder& return_value_uint8(uint8_t val) + { + if (validateOperationExists()) + { + operationResponse.operations.back().parameters.push_back(val); + } + return *this; + }; + + OperationResponseBuilder& return_value_uint16(uint16_t val) + { + if (validateOperationExists()) + { + operationResponse.operations.back().parameters.push_back(val); + } + return *this; + }; + + OperationResponseBuilder& return_value_uint32(uint32_t val) + { + if (validateOperationExists()) + { + operationResponse.operations.back().parameters.push_back(val); + } + return *this; + }; + + OperationResponseBuilder& return_value_uint64(uint64_t val) + { + if (validateOperationExists()) + { + operationResponse.operations.back().parameters.push_back(val); + } + return *this; + }; + + OperationResponseBuilder& return_value_data_buffer_out(std::vector&& data) + { + if (validateOperationExists()) + { + operationResponse.operations.back().parameters.push_back(OwnedBuffer{std::move(data)}); + } + return *this; + }; + + OperationResponseBuilder& return_crypto_operation_response(const OperationIdentifier& opId, + OperationResult res, + common::ResponseParameters&& response) + { + operation(opId); + if (res == OPERATION_RESULT_SUCCESS) + { + return_success(); + } + else + { + return_error(static_cast(res)); + } + operationResponse.operations.back().parameters = std::move(response); + return *this; + }; + + Expected build() + { + if (error) + { + std::cout << "[CONTROL_OP_RESP_BUILDER] ERROR - Cannot build OperationResponse due to previous errors in " + "building process" + << "\n"; + + error = false; + operationResponse.operations.clear(); + + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInternalError); + } + + return OperationResponse{ + std::move(operationResponse.operations), + }; + } + + private: + bool error = false; + + OperationResponse operationResponse; + + bool validateOperationExists() + { + if (operationResponse.operations.empty()) + { + std::cout << "[CONTROL_OP_RESP_BUILDER] ERROR - Trying to add parameter without an operation" + << "\n"; + error = true; + return false; + } + return true; + } +}; + +// ============================================================================ +// Response Validator (Fluent Builder Pattern) +// ============================================================================ + +class ControlResponseValidator +{ + public: + explicit ControlResponseValidator(const OperationResponse& response, bool logErrors = false) + : m_response(response), + m_currentOpIndex(std::numeric_limits::max()), + m_errorMsg(""), + m_logErrors(logErrors) + { + } + + /// Create a validator from a Result/Expected type, validating has_value() first + /// Works with any type T where has_value() and value() are available + /// Automatically extracts .operation from ControlResponse if present + template + static ControlResponseValidator FromResult(const T& result, bool logErrors = false) + { + if (!result.has_value()) + { + ControlResponseValidator validator(OperationResponse{}, logErrors); + validator.m_isValid = false; + validator.m_errorMsg = "Operation response is not available (has_value() returned false)"; + return validator; + } + + // Extract .operation from ControlResponse + ControlResponseValidator validator(result.value().operation, logErrors); + return validator; + } + + // ======================================================================== + // Operation Switching + // ======================================================================== + + /// Start validating a new operation (implicit index increment) + ControlResponseValidator& expectOperation(const OperationIdentifier& opId) + { + if (!m_isValid) + return *this; + + // Increment to next operation + if (m_currentOpIndex == std::numeric_limits::max()) + { + m_currentOpIndex = 0; + } + else + { + m_currentOpIndex++; + } + + // Validate we have enough operations + if (m_currentOpIndex >= m_response.get().operations.size()) + { + m_isValid = false; + m_errorMsg = "Expected operation at index " + std::to_string(m_currentOpIndex) + " but response has only " + + std::to_string(m_response.get().operations.size()) + " operations"; + return *this; + } + + // Validate operation ID + const auto& op = m_response.get().operations[m_currentOpIndex]; + if (op.operationId.operationActor != opId.operationActor || + op.operationId.operationAction != opId.operationAction) + { + m_isValid = false; + m_errorMsg = "Operation at index " + std::to_string(m_currentOpIndex) + + " ID mismatch. Expected actor=" + std::to_string(opId.operationActor) + + " action=" + std::to_string(opId.operationAction) + + " but got actor=" + std::to_string(op.operationId.operationActor) + + " action=" + std::to_string(op.operationId.operationAction); + return *this; + } + + return *this; + } + + // ======================================================================== + // Result Validation (applies to current operation) + // ======================================================================== + + /// Validate current operation succeeded (result code = 0) + ControlResponseValidator& expectSuccess() + { + if (!m_isValid) + return *this; + + const auto& op = m_response.get().operations[m_currentOpIndex]; + if (op.result != OPERATION_RESULT_SUCCESS) + { + auto errorCode = static_cast(op.result); + + m_isValid = false; + m_errorMsg = "Operation at index " + std::to_string(m_currentOpIndex) + " failed with error code " + + std::string(score::mw::crypto::kCryptoErrorDomain.MessageFor( + static_cast(errorCode))); + } + + return *this; + } + + // ======================================================================== + // Parameter Verification & Extraction (applies to specific operation) + // ======================================================================== + + /// Query if parameter at specified operation and parameter index is of requested type + template + Expected isParameterOfType(size_t opIndex, size_t paramIdx) + { + if (!m_isValid) + { + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInternalError); + } + + if (opIndex >= m_response.get().operations.size()) + { + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + } + + const auto& op = m_response.get().operations[opIndex]; + + if (paramIdx >= op.parameters.size()) + { + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + } + + return std::holds_alternative(op.parameters[paramIdx]); + } + + /// Extract parameter at specified operation and parameter index with type checking + template + Expected getParameterAt(size_t opIndex, size_t paramIdx) + { + if (!m_isValid) + { + logError(); + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInternalError); + } + + if (opIndex >= m_response.get().operations.size()) + { + m_isValid = false; + m_errorMsg = "Operation index " + std::to_string(opIndex) + " out of bounds"; + logError(); + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + } + + const auto& op = m_response.get().operations[opIndex]; + + if (paramIdx >= op.parameters.size()) + { + m_isValid = false; + m_errorMsg = "Parameter index " + std::to_string(paramIdx) + " out of bounds"; + logError(); + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + } + + if (!std::holds_alternative(op.parameters[paramIdx])) + { + m_isValid = false; + m_errorMsg = "Parameter type mismatch at operation " + std::to_string(opIndex) + " parameter " + + std::to_string(paramIdx); + logError(); + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + } + + return std::get(op.parameters[paramIdx]); + } + + // ======================================================================== + // Status Query + // ======================================================================== + + bool isValid() const + { + return m_isValid; + } + const std::string& getError() const + { + return m_errorMsg; + } + + private: + std::reference_wrapper m_response; + size_t m_currentOpIndex; + bool m_isValid = true; + std::string m_errorMsg; + bool m_logErrors = false; + + void logError() + { + if (m_logErrors) + { + std::cout << "[ControlResponseValidator] ERROR - " << m_errorMsg << "\n"; + } + } +}; + +// ============================================================================ +// ControlRequest Builder (Fluent API) +// ============================================================================ + +/** + * @class ControlRequestBuilder + * @brief Fluent builder for constructing ControlRequest objects. + * + * Provides a convenient fluent API for building ControlRequest objects with + * automatic operation and parameter configuration. Delegates to an internal + * OperationRequestBuilder for operation handling. + * + * Usage: + * auto request = ControlRequestBuilder() + * .forDataNodeId(node_id) + * .operation(my_operation) + * .with_in_string("param") + * .build(); + */ +class ControlRequestBuilder +{ + public: + ControlRequestBuilder() : m_request{} + { + // Filled in by the IPC during transmission + m_request.request_id = 0; + m_request.pid = 0; + m_request.uid = 0; + + // Default value, in case not provided + m_request.data_node_id = 0; + } + + /** + * @brief Set the data node ID for this control request. + * + * @param data_node_id The data node ID to associate with this request. + * @return Reference to this builder for method chaining. + */ + ControlRequestBuilder& forDataNodeId(daemon::data_manager::DataNodeId data_node_id) + { + m_request.data_node_id = data_node_id; + return *this; + } + + /** + * @brief Start a new operation in the request. + * + * Delegates to the internal OperationRequestBuilder. + * + * @param opId The operation identifier. + * @return Reference to this builder for method chaining. + */ + ControlRequestBuilder& operation(const protocol::OperationIdentifier& opId) + { + m_op_builder.operation(opId); + return *this; + } + + /** + * @brief Add a no-parameter operation parameter. + * + * @return Reference to this builder for method chaining. + */ + ControlRequestBuilder& with_no_param() + { + m_op_builder.with_no_param(); + return *this; + } + + /** + * @brief Add a string input parameter. + * + * @param string The string parameter value. + * @return Reference to this builder for method chaining. + */ + ControlRequestBuilder& with_in_string(std::string_view string) + { + m_op_builder.with_in_string(string); + return *this; + } + + /** + * @brief Add a data buffer input parameter. + * + * @param data The buffer data. + * @return Reference to this builder for method chaining. + */ + ControlRequestBuilder& with_in_data_buffer(span data) + { + m_op_builder.with_in_data_buffer(data); + return *this; + } + + /** + * @brief Add a bool value input parameter. + * + * @param val The bool value. + * @return Reference to this builder for method chaining. + */ + ControlRequestBuilder& with_in_val_bool(bool val) + { + m_op_builder.with_in_val_bool(val); + return *this; + } + + /** + * @brief Add an uint8 value input parameter. + * + * @param val The uint8 value. + * @return Reference to this builder for method chaining. + */ + ControlRequestBuilder& with_in_val_uint8(uint8_t val) + { + m_op_builder.with_in_val_uint8(val); + return *this; + } + + /** + * @brief Add an uint16 value input parameter. + * + * @param val The uint16 value. + * @return Reference to this builder for method chaining. + */ + ControlRequestBuilder& with_in_val_uint16(uint16_t val) + { + m_op_builder.with_in_val_uint16(val); + return *this; + } + + /** + * @brief Add an uint32 value input parameter. + * + * @param val The uint32 value. + * @return Reference to this builder for method chaining. + */ + ControlRequestBuilder& with_in_val_uint32(uint32_t val) + { + m_op_builder.with_in_val_uint32(val); + return *this; + } + + /** + * @brief Add an uint64 value input parameter. + * + * @param val The uint64 value. + * @return Reference to this builder for method chaining. + */ + ControlRequestBuilder& with_in_val_uint64(uint64_t val) + { + m_op_builder.with_in_val_uint64(val); + return *this; + } + + /** + * @brief Build the ControlRequest. + * + * Finalizes the internal OperationRequestBuilder and constructs the + * ControlRequest with all accumulated settings. + * + * @return Expected + * or an error if the operation builder encountered issues. + */ + Expected build() + { + auto op_result = m_op_builder.build(); + if (!op_result.has_value()) + { + return make_unexpected(op_result.error()); + } + + m_request.operation = op_result.value(); + return m_request; + } + + private: + ControlRequest m_request; + protocol::OperationRequestBuilder m_op_builder; +}; + +} // namespace score::crypto::daemon::control_plane::protocol + +#endif // SCORE_CRYPTO_DAEMON_CONTROL_PLANE_CONTROL_PROTOCOL_H diff --git a/score/crypto/daemon/control_plane/i_control_server.h b/score/crypto/daemon/control_plane/i_control_server.h new file mode 100644 index 0000000..9376410 --- /dev/null +++ b/score/crypto/daemon/control_plane/i_control_server.h @@ -0,0 +1,40 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CONTROL_SERVER_H +#define CONTROL_SERVER_H + +#include + +namespace score::crypto::daemon::control_plane +{ + +/// Abstract interface for control plane server lifecycle management +class IControlServer +{ + public: + virtual ~IControlServer() = default; + + /// Start the server listening on the specified socket path + /// @param socket_path Path to Unix domain socket + virtual void Start(std::string_view socket_path) = 0; + + /// Block until server termination + virtual void WaitForTermination() = 0; + + /// Stop the server gracefully, cleaning up resources + virtual void Stop() = 0; +}; + +} // namespace score::crypto::daemon::control_plane + +#endif // CONTROL_SERVER_H diff --git a/score/crypto/daemon/control_plane/i_handler_chain_factory.hpp b/score/crypto/daemon/control_plane/i_handler_chain_factory.hpp new file mode 100644 index 0000000..0aa1811 --- /dev/null +++ b/score/crypto/daemon/control_plane/i_handler_chain_factory.hpp @@ -0,0 +1,64 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_CONTROL_PLANE_REQUEST_HANDLER_FACTORY_HPP_ +#define SCORE_CRYPTO_DAEMON_CONTROL_PLANE_REQUEST_HANDLER_FACTORY_HPP_ + +#include "score/crypto/daemon/control_plane/i_request_handler.hpp" +#include + +namespace score::crypto::daemon::control_plane +{ + +/** + * @interface IHandlerChainFactory + * @brief Factory interface for creating IRequestHandler instances. + * + * This interface enables creation of handler instances per thread context, + * supporting thread-safe request processing with isolated handler state. + */ +class IHandlerChainFactory +{ + public: + /** + * @brief Default constructor. + */ + IHandlerChainFactory() = default; + + /** + * @brief Virtual destructor. + */ + virtual ~IHandlerChainFactory() = default; + + // Non-copyable - factories should not be duplicated + IHandlerChainFactory(const IHandlerChainFactory&) = delete; + IHandlerChainFactory& operator=(const IHandlerChainFactory&) = delete; + + // Movable - factories can be transferred (unique_ptr ownership transfer) + + /** + * @brief Creates a new request handler instance. + * + * Each call creates a fresh handler with the complete chain of responsibility. + * This enables per-thread handler isolation in multi-threaded environments. + * + * @return std::unique_ptr New handler instance + * + * @par Thread-safety Requirements + * Implementations must be thread-safe + */ + virtual std::unique_ptr CreateRequestHandler() = 0; +}; + +} // namespace score::crypto::daemon::control_plane + +#endif // SCORE_CRYPTO_DAEMON_CONTROL_PLANE_REQUEST_HANDLER_FACTORY_HPP_ diff --git a/score/crypto/daemon/control_plane/i_request_handler.hpp b/score/crypto/daemon/control_plane/i_request_handler.hpp new file mode 100644 index 0000000..7707218 --- /dev/null +++ b/score/crypto/daemon/control_plane/i_request_handler.hpp @@ -0,0 +1,67 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_DAEMON_CONTROL_PLANE_REQUEST_HANDLER_HPP_ +#define CRYPTO_DAEMON_CONTROL_PLANE_REQUEST_HANDLER_HPP_ + +#include + +#include "score/crypto/daemon/control_plane/control_protocol.h" + +namespace score::crypto::daemon::control_plane +{ + +using SingleOperationRequest = protocol::SingleOperationRequest; +using SingleOperationResponse = protocol::SingleOperationResponse; + +using ControlRequest = protocol::ControlRequest; +using ControlResponse = protocol::ControlResponse; + +/** + * @interface IRequestHandler + * @brief Base interface for chain of responsibility pattern in request processing. + * + * This interface provides a common contract for all elements in the request + * processing chain (brokers, mediators, etc.). It enables dynamic insertion + * and deletion of handlers in the chain. + */ +class IRequestHandler +{ + public: + /** + * @brief Default constructor. + */ + IRequestHandler() = default; + + /** + * @brief Virtual destructor. + */ + virtual ~IRequestHandler() = default; + + // Delete copy and move operations + IRequestHandler(const IRequestHandler&) = delete; + IRequestHandler& operator=(const IRequestHandler&) = delete; + IRequestHandler(IRequestHandler&&) = delete; + IRequestHandler& operator=(IRequestHandler&&) = delete; + + /** + * @brief Processes a control request and generates a corresponding response. + * + * @param request The control request to be processed. + * @return ControlResponse The response generated after processing the request. + */ + virtual ControlResponse processRequest(const ControlRequest& request) = 0; +}; + +} // namespace score::crypto::daemon::control_plane + +#endif // CRYPTO_DAEMON_CONTROL_PLANE_REQUEST_HANDLER_HPP_ diff --git a/score/crypto/daemon/control_plane/src/basic_handler_chain_factory.cpp b/score/crypto/daemon/control_plane/src/basic_handler_chain_factory.cpp new file mode 100644 index 0000000..e5ce0d9 --- /dev/null +++ b/score/crypto/daemon/control_plane/src/basic_handler_chain_factory.cpp @@ -0,0 +1,52 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/control_plane/basic_handler_chain_factory.hpp" +#include "score/crypto/daemon/config/inc/config.hpp" +#include "score/crypto/daemon/control_plane/i_handler_chain_factory.hpp" +#include "score/crypto/daemon/control_plane/i_request_handler.hpp" +#include "score/crypto/daemon/control_plane/src/connection_handler.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" +#include "score/crypto/daemon/mediator/src/mediator_impl.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" + +#include +#include + +namespace score::crypto::daemon::control_plane +{ + +BasicHandlerChainFactory::BasicHandlerChainFactory(std::shared_ptr data_manager, + std::shared_ptr provider_manager, + const config::Config& config, + key_management::KeyManagementService::Sptr km_service) + : IHandlerChainFactory(), + m_data_manager(std::move(data_manager)), + m_provider_manager(std::move(provider_manager)), + m_config(config), + m_km_service(std::move(km_service)) +{ +} + +std::unique_ptr BasicHandlerChainFactory::CreateRequestHandler() +{ + // whole chain i.e mediator and ConnectionHandler are created per invocation + auto mediator = + std::make_unique(m_data_manager, m_provider_manager, m_config, m_km_service); + auto handler = std::make_unique(std::move(mediator), + m_data_manager, // Shared data manager + m_config); + + return handler; +} + +} // namespace score::crypto::daemon::control_plane diff --git a/score/crypto/daemon/control_plane/src/connection_handler.cpp b/score/crypto/daemon/control_plane/src/connection_handler.cpp new file mode 100644 index 0000000..f02b701 --- /dev/null +++ b/score/crypto/daemon/control_plane/src/connection_handler.cpp @@ -0,0 +1,162 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/control_plane/src/connection_handler.hpp" +#include "score/crypto/daemon/common/actors.hpp" +#include "score/crypto/daemon/control_plane/control_operations.h" +#include "score/crypto/daemon/control_plane/i_request_handler.hpp" +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" + +#include +#include +#include + +namespace score::crypto::daemon::control_plane +{ + +ConnectionHandler::ConnectionHandler(std::unique_ptr next_request_handler, + std::shared_ptr data_manager, + const config::Config& config) + : m_next_request_handler(std::move(next_request_handler)), m_data_manager(data_manager) +{ + (void)config; // For future use +} + +ControlResponse ConnectionHandler::processRequest(const ControlRequest& request) +{ + std::cout << "[CONTROL_HANDLER] Received request - Request ID: " << request.request_id + << ", Client Id ID: " << request.client_id << ", UID: " << request.uid << ", PID: " << request.pid + << ", Data Node Id: " << request.data_node_id << ", Data Node Value: " << request.node_id_value + << ", Data Node Tag: " << request.node_tag_value << "\n"; + std::cout << "[CONTROL_HANDLER] Thread ID: " << std::this_thread::get_id() << "\n"; + // Validate empty request + if (request.operation.operations.empty()) + { + std::cout << "[CONTROL_HANDLER] Empty operation, returning error\n"; + protocol::OperationResponseBuilder builder; + auto opResponse = builder.build().value(); + return ControlResponse{request.request_id, opResponse}; + } + + // TODO: This probably also needs to be refactored. + // Currently, we are only processing the first operation in the request. + // We should iterate through all operations and process them accordingly. + // But that also means we need the response builder here and forward it + // Otherwise, we need to limit what types of request can be combined in one batch + + // This means IRequestHandler::processRequest needs to be refactored + + protocol::OperationResponseBuilder responseBuilder; + + // Iterate through operations in request - process those for the control plane + for (size_t idx = 0; idx < request.operation.operations.size(); ++idx) + { + // Extract operation name + const auto& opId = request.operation.operations[idx].operationId; + std::cout << "[CONTROL_HANDLER] Operation actor:" << opId.operationActor << " action:" << opId.operationAction + << "\n"; + if (opId.operationActor != common::actors::OP_ACTOR_CONTROL) + { + // Forward to next handler + return m_next_request_handler->processRequest(request); + } + else + { + if (!ProcessSingleRequest(request, request.operation.operations[idx].operationId, responseBuilder)) + { + std::cerr << "[CONTROL_HANDLER] Failed to process control operation: actor=" << opId.operationActor + << " action=" << opId.operationAction << "\n"; + std::cerr << "[CONTROL_HANDLER] Stopping further processing of operations in this request\n"; + break; + } + } + } + + return ControlResponse{request.request_id, + responseBuilder.build().value_or(control_plane::protocol::OperationResponse())}; +} + +bool ConnectionHandler::ProcessSingleRequest(const ControlRequest& request, + const common::OperationIdentifier& opId, + control_plane::protocol::OperationResponseBuilder& responseBuilder) +{ + std::cout << "[CONTROL_HANDLER] Processing control operation: actor=" << opId.operationActor + << " action=" << opId.operationAction << "\n"; + + if (opId.operationAction == operations::CONNECTION_OPEN) + { + return ProcessConnectionCreation(request, opId, responseBuilder); + } + if (opId.operationAction == operations::CONNECTION_CLOSE) + { + return ProcessConnectionClosure(request, opId, responseBuilder); + } + + return ProcessUnknownRequest(request, opId, responseBuilder); +} + +bool ConnectionHandler::ProcessConnectionCreation(const ControlRequest& request, + const common::OperationIdentifier& opId, + control_plane::protocol::OperationResponseBuilder& responseBuilder) +{ + // Create root node via DataManager - returns assigned DataNodeId + auto node = std::make_shared(); + auto dataNodeIdRes = m_data_manager->addNode(request.client_id, node); + if (!dataNodeIdRes.has_value()) + { + std::cerr << "[CONTROL_HANDLER] Failed to create connection"; + responseBuilder.operation(opId).return_error(dataNodeIdRes.error()); + return false; + } + + auto connection_id = dataNodeIdRes.value(); + + std::cout << "[CONTROL_HANDLER] Created connection with connection_id: " << connection_id << "\n"; + + // Build success response with connection_id + responseBuilder.operation(opId).return_success().return_value_uint64(connection_id); + + return true; +} + +bool ConnectionHandler::ProcessConnectionClosure(const ControlRequest& request, + const common::OperationIdentifier& opId, + control_plane::protocol::OperationResponseBuilder& responseBuilder) +{ + auto connection_id = request.data_node_id; + auto client_id = request.client_id; + + std::cout << "[CONTROL_HANDLER] Closing client_id: " << client_id << " connection_id: " << connection_id << "\n"; + + // Clear all contexts associated with this connection by removing the connection node + // This will cascade delete all child context nodes + auto result = m_data_manager->deleteNode(client_id, connection_id); + if (!result.has_value()) + { + std::cout << "[CONTROL_HANDLER] Warning Connection_id: " << connection_id << " not found"; + } + + responseBuilder.operation(opId).return_success(); + return true; +} + +bool ConnectionHandler::ProcessUnknownRequest(const ControlRequest& request, + const common::OperationIdentifier& opId, + control_plane::protocol::OperationResponseBuilder& responseBuilder) +{ + std::cout << "[CONTROL_HANDLER] Received unknown operationAction=" << opId.operationAction << "\n"; + responseBuilder.operation(opId).return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return true; +} + +} // namespace score::crypto::daemon::control_plane diff --git a/score/crypto/daemon/control_plane/src/connection_handler.hpp b/score/crypto/daemon/control_plane/src/connection_handler.hpp new file mode 100644 index 0000000..2ce3105 --- /dev/null +++ b/score/crypto/daemon/control_plane/src/connection_handler.hpp @@ -0,0 +1,66 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CONTROL_HANDLER_IMPL_H +#define CONTROL_HANDLER_IMPL_H + +#include + +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/config/inc/config.hpp" +#include "score/crypto/daemon/control_plane/i_request_handler.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" + +namespace score::crypto::daemon::control_plane +{ + +class ConnectionHandler : public IRequestHandler +{ + public: + ConnectionHandler(std::unique_ptr next_request_handler, + std::shared_ptr data_manager, + const config::Config& config); + + ~ConnectionHandler() override = default; + + // Move semantics - handler is move-only due to unique_ptr member + ConnectionHandler(ConnectionHandler&&) = default; + ConnectionHandler& operator=(ConnectionHandler&&) = default; + + // Delete copy semantics + ConnectionHandler(const ConnectionHandler&) = delete; + ConnectionHandler& operator=(const ConnectionHandler&) = delete; + + ControlResponse processRequest(const ControlRequest& request) override; + + private: + std::unique_ptr m_next_request_handler; + std::shared_ptr m_data_manager; + + bool ProcessSingleRequest(const ControlRequest& request, + const common::OperationIdentifier& opId, + control_plane::protocol::OperationResponseBuilder& responseBuilder); + + bool ProcessConnectionCreation(const ControlRequest& request, + const common::OperationIdentifier& opId, + control_plane::protocol::OperationResponseBuilder& responseBuilder); + bool ProcessConnectionClosure(const ControlRequest& request, + const common::OperationIdentifier& opId, + control_plane::protocol::OperationResponseBuilder& responseBuilder); + bool ProcessUnknownRequest(const ControlRequest& request, + const common::OperationIdentifier& opId, + control_plane::protocol::OperationResponseBuilder& responseBuilder); +}; + +} // namespace score::crypto::daemon::control_plane + +#endif // CONTROL_HANDLER_IMPL_H diff --git a/score/crypto/daemon/data_manager/BUILD b/score/crypto/daemon/data_manager/BUILD new file mode 100644 index 0000000..27d3cdd --- /dev/null +++ b/score/crypto/daemon/data_manager/BUILD @@ -0,0 +1,35 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +cc_library( + name = "data_manager", + srcs = [ + "src/data_manager.cpp", + "src/data_node.cpp", + "src/data_node_manager_token.hpp", + ], + hdrs = [ + "i_data_manager.hpp", + "data_node.hpp", + "data_node_accessor.hpp", + # Public due to missing Factory + "data_manager.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/common:common_types", + "//score/crypto/daemon/common", + "//score/crypto/daemon/provider/handler:handler_headers", + ], +) diff --git a/score/crypto/daemon/data_manager/data_manager.hpp b/score/crypto/daemon/data_manager/data_manager.hpp new file mode 100644 index 0000000..b26f2f6 --- /dev/null +++ b/score/crypto/daemon/data_manager/data_manager.hpp @@ -0,0 +1,203 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_DAEMON_DATA_MANAGER_DATA_MANAGER_HPP_ +#define CRYPTO_DAEMON_DATA_MANAGER_DATA_MANAGER_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" + +namespace score::crypto::daemon::data_manager +{ + +/** + * @brief Thread-safe concrete implementation of IDataManager. + * + * Manages hierarchical data nodes with unique identification, combining + * a client-provided identifier and a manager-assigned per-client counter. + * + * @par Internal structure + * - Primary index: @c ClientId → vector of root (entry) node IDs. + * - Secondary index: @c ClientId × @c DataNodeId → shared_ptr for O(1) lookup. + * - Parent–child relationships are maintained through the DataNode interface. + * - Busy-node set tracks nodes currently held by a DataNodeAccessor with exclusive access. + * + * @par Thread safety + * All public operations are serialised by an internal mutex. + */ +class DataManager : public IDataManager +{ + public: + DataManager() = default; + ~DataManager() override = default; + + // Prevent copy/move operations + DataManager(const DataManager&) = delete; + DataManager& operator=(const DataManager&) = delete; + DataManager(DataManager&&) = delete; + DataManager& operator=(DataManager&&) = delete; + + /// @copydoc IDataManager::addNode + Expected addNode( + ClientId clientId, + std::shared_ptr node) override; + + /// @copydoc IDataManager::addChildNode + Expected + addChildNode(ClientId clientId, DataNodeId parentId, std::shared_ptr node) override; + + /// @copydoc IDataManager::addSiblingNode + Expected + addSiblingNode(ClientId clientId, DataNodeId siblingId, std::shared_ptr node) override; + + /// @copydoc IDataManager::deleteNode + Expected deleteNode(ClientId clientId, + DataNodeId nodeId) override; + + /// @copydoc IDataManager::deleteClientNodes + Expected deleteClientNodes( + ClientId clientId) override; + + /// @copydoc IDataManager::releaseNodeAccessor + Expected releaseNodeAccessor( + ClientId clientId, + DataNodeId nodeId) const override; + + /// @copydoc IDataManager::getNodeAccessor + Expected, score::crypto::daemon::common::DaemonErrorCode> getNodeAccessor( + ClientId clientId, + DataNodeId nodeId) const override; + + private: + /// @brief Mutex serialising all public operations. + mutable std::mutex m_mutex; + + /// @brief Maps each client to the ordered list of its root (entry) node IDs. + std::unordered_map> m_entry_nodes_per_client; + + /// @brief Full node storage: ClientId → (DataNodeId → shared_ptr). + std::unordered_map> m_node_map; + + /// @brief Monotonically-increasing ID counter per client, wrapping at DATA_NODE_VALUE_MAX. + std::unordered_map m_id_counter_per_client; + + /// @brief Tracks nodes that are currently held by an exclusive DataNodeAccessor. + /// Mutable to allow releaseNodeAccessor() to modify it from const methods. + mutable std::unordered_map> m_busy_nodes; + + /** + * @brief Removes @p nodeId from the root-node index of @p clientId. + * + * Also removes the entire client entry from the index when the resulting + * vector becomes empty. + * + * @pre The caller must hold @p _lock on @c m_mutex. + * @param clientId Owning client identifier. + * @param nodeId Root-level node ID to remove. + * @param _lock Proof that the caller holds the mutex. + * @return @c std::monostate on success, or @c + * score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument if either @p clientId or @p nodeId is not found. + */ + Expected + removeEntryNodeFromIndex(ClientId clientId, DataNodeId nodeId, const std::lock_guard& _lock); + + /** + * @brief Recursively removes @p rootNodeId and all of its descendants from storage. + * + * Performs a depth-first traversal, collecting nodes in post-order, then + * calls removeSingleNodeFromStorage() for each one so that leaf nodes are + * erased before their ancestors. + * + * @pre The caller must hold @p _lock on @c m_mutex. + * @param clientId Owning client identifier. + * @param rootNodeId Root of the subtree to remove. + * @param _lock Proof that the caller holds the mutex. + * @return @c std::monostate on success, or the first @c score::crypto::daemon::common::DaemonErrorCode + * encountered while processing the subtree. + */ + Expected + removeNodesFromStorage(ClientId clientId, DataNodeId rootNodeId, const std::lock_guard& _lock); + + /** + * @brief Removes a single node from storage and detaches it from its parent. + * + * If the node has a parent, it is removed from the parent's child list via + * DataNode::removeChild(). If it is a root-level entry node, it is removed + * from the entry-node index via removeEntryNodeFromIndex(). In both cases the + * node is then erased from @c m_node_map and @c m_busy_nodes. + * + * @pre The caller must hold @p _lock on @c m_mutex. + * @param node Shared pointer to the node to remove. + * @param clientId Owning client identifier. + * @param nodeId @c DataNodeId of the node to remove. + * @param _lock Proof that the caller holds the mutex. + * @return @c std::monostate on success, or an @c score::crypto::daemon::common::DaemonErrorCode on + * failure. + */ + Expected removeSingleNodeFromStorage( + const std::shared_ptr& node, + ClientId clientId, + DataNodeId nodeId, + const std::lock_guard& _lock); + + /** + * @brief Looks up an existing node. + * + * @pre The caller must hold @p _lock on @c m_mutex. + * @param clientId Owning client identifier. + * @param nodeId @c DataNodeId of the node to look up. + * @param _lock Proof that the caller holds the mutex. + * @return Shared pointer to the node on success, or + * @c score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument if not found. + */ + Expected, score::crypto::daemon::common::DaemonErrorCode> + getNodeLocked(ClientId clientId, DataNodeId nodeId, const std::lock_guard& _lock) const; + + /** + * @brief Returns the next available @c DataNodeId for the given client. + * + * Increments the per-client counter, wrapping at @c DATA_NODE_VALUE_MAX, and + * retries up to a fixed limit until a free ID (not already present in + * @c m_node_map) is found. + * + * @pre The caller must hold @p _lock on @c m_mutex. + * @param clientId Owning client identifier. + * @param _lock Proof that the caller holds the mutex. + * @return A free @c DataNodeId on success, or + * @c score::crypto::daemon::common::DaemonErrorCode::kQuotaExceeded if no free ID is found within + * the maximum number of retries. + */ + Expected getNextNodeIdLocked( + ClientId clientId, + const std::lock_guard& _lock); + + /// @brief Limit of nodes per client, used to bound the number of retries in getNextNodeIdLocked(). + const std::size_t m_max_nodes_per_client = 1000; + + /// @brief Log prefix prepended to all diagnostic messages emitted by this class. + static constexpr std::string_view LOG_PREFIX = "[DATA_MANAGER] "; +}; + +} // namespace score::crypto::daemon::data_manager + +#endif // CRYPTO_DAEMON_DATA_MANAGER_DATA_MANAGER_HPP_ diff --git a/score/crypto/daemon/data_manager/data_node.hpp b/score/crypto/daemon/data_manager/data_node.hpp new file mode 100644 index 0000000..54720ae --- /dev/null +++ b/score/crypto/daemon/data_manager/data_node.hpp @@ -0,0 +1,226 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_DAEMON_DATA_MANAGER_DATA_NODE_HPP_ +#define CRYPTO_DAEMON_DATA_MANAGER_DATA_NODE_HPP_ + +#include +#include +#include +#include +#include +#include + +#include "score/crypto/common/types.hpp" + +#include "score/crypto/daemon/common/daemon_error.hpp" + +namespace score::crypto::daemon::data_manager +{ + +/// @brief Opaque 64-bit identifier assigned to every DataNode by the DataManager. +using DataNodeId = std::uint64_t; + +/// @brief Opaque 64-bit identifier that represents the owning client of a node. +using ClientId = std::uint64_t; + +/// @brief Discriminator for concrete DataNode subtypes. +/// +/// Provides a MISRA-compliant way to determine a node's logical type +/// before performing a dynamic downcast. Avoids relying on RTTI for +/// branching decisions (MISRA C++ 2023 Rule 11.0.1). +enum class DataNodeType : std::uint8_t +{ + kGeneric = 0U, ///< Base or unspecialised node + kConnection = 1U, ///< IPC connection root node + kContext = 2U, ///< Crypto operation context (ContextDataNode) + kKeySlot = 3U, ///< Key slot reference (KeySlotDataNode) + kKeyData = 4U, ///< Loaded-key reference (KeyDataNode) +}; + +/** + * @brief Base class for all concrete data-node implementations. + * + * Provides common functionality for managing parent–child relationships, + * the node identifier, and the owning client identifier. + * Derived classes are expected to release their own resources in their destructor. + * + * @par Ownership model + * DataNodes can hold further DataNodes as children, thereby forming tree structures. + * Cyclic graphs are not supported and may lead to undefined behaviour. + * + * @par Exclusive access + * A node constructed with @p exclusiveAccess = @c true (the default) may only be held + * by a single DataNodeAccessor at a time. The DataManager enforces this via its + * busy-node set. Set @p exclusiveAccess = @c false for nodes that may be accessed + * concurrently. + * + * @par Thread safety + * Parent–child mutation operations require a @c DataNodeManagerToken, which + * can only be created by the DataManager. The DataManager serialises all such + * calls under its own mutex, so DataNode itself does not carry an internal mutex. + * @c getNodeId() and @c getClientId() use atomic loads and are therefore lock-free. + */ + +class DataNodeManagerToken; + +class DataNode +{ + public: + using Sptr = std::shared_ptr; + + /** + * @brief Constructs a node with an uninitialized node ID. + * + * The DataManager assigns the definitive @c DataNodeId and @c ClientId + * via setNodeId() and setClientId() after determining the next available counter value. + * + * @param exclusiveAccess When @c true (default), only one DataNodeAccessor may hold + * this node at a time. Pass @c false for shared access. + */ + explicit DataNode(bool exclusiveAccess = true); + + virtual ~DataNode() = default; + + /// @brief Returns the logical type of this node. + /// + /// Derived classes override to return their specific type tag. + /// Default implementation returns @c DataNodeType::kGeneric. + [[nodiscard]] virtual DataNodeType GetNodeType() const noexcept + { + return DataNodeType::kGeneric; + } + + // Prevent copy/move operations + DataNode(const DataNode&) = delete; + DataNode& operator=(const DataNode&) = delete; + DataNode(DataNode&&) = delete; + DataNode& operator=(DataNode&&) = delete; + + /** + * @brief Returns the @c DataNodeId assigned by the DataManager. + * @return Current node identifier (0 if not yet assigned). + */ + DataNodeId getNodeId() const; + + /** + * @brief Sets the @c DataNodeId. + * + * Only the DataManager may call this method; the required @p token can only be + * constructed by DataManager (friend relationship). + * + * @param nodeId New node identifier. + * @param token Access token proving the caller is the DataManager. + */ + void setNodeId(DataNodeId nodeId, const DataNodeManagerToken& token); + + /** + * @brief Returns the @c ClientId of the owning client. + * @return Current client identifier (0 if not yet assigned). + */ + ClientId getClientId() const; + + /** + * @brief Sets the owning @c ClientId. + * + * Only the DataManager may call this method; the required @p token can only be + * constructed by DataManager. + * + * @param clientId New client identifier. + * @param token Access token proving the caller is the DataManager. + */ + void setClientId(ClientId clientId, const DataNodeManagerToken& token); + + /** + * @brief Sets the parent of this node. + * + * Stores a weak pointer to @p parent. + * + * @param parent Weak pointer to the parent node. + * @param token Access token proving the caller is the DataManager. + */ + void setParent(const std::weak_ptr& parent, const DataNodeManagerToken& token); + + /** + * @brief Returns the @c DataNodeId of the parent node. + * @param token Access token proving the caller is the DataManager. + * @return The parent's @c DataNodeId on success, or @c + * score::crypto::daemon::common::DaemonErrorCode::kInvalidContext) if this node has no parent (or the parent has + * already been destroyed). + */ + Expected getParent( + const DataNodeManagerToken& token) const; + + /** + * @brief Appends @p child to the ordered list of this node's children. + * + * @param child Shared pointer to the child node to add. + * @param token Access token proving the caller is the DataManager. + */ + void addChild(const std::shared_ptr& child, const DataNodeManagerToken& token); + + /** + * @brief Removes the child identified by @p nodeId from this node's child list. + * + * @param nodeId @c DataNodeId of the child to remove. + * @param token Access token proving the caller is the DataManager. + * @return @c std::monostate on success, or @c + * score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument if no child with @p nodeId exists. + */ + Expected removeChild( + DataNodeId nodeId, + const DataNodeManagerToken& token); + + /** + * @brief Returns the @c DataNodeId values of all direct children. + * + * The returned vector is a snapshot. Thread safety is guaranteed by the + * DataManager, which holds its own mutex for the duration of the call. + * + * @param token Access token proving the caller is the DataManager. + * @return Vector of child node IDs in insertion order. + */ + std::vector getChildren(const DataNodeManagerToken& token) const; + + /** + * @brief Indicates whether this node requires exclusive access. + * + * When @c true, the DataManager allows at most one DataNodeAccessor to hold + * this node at a time. + * + * @return @c true if exclusive access is required; @c false otherwise. + */ + bool requiresExclusiveAccess() const; + + private: + /// @brief Manager-assigned unique identifier for this node. + std::atomic m_nodeId = 0; + + /// @brief Identifier of the client that owns this node. + std::atomic m_client_id = 0; + + /// @brief Weak reference to the parent node (empty for root nodes). + std::weak_ptr m_parent; + + /// @brief Ordered list of direct child nodes (strong ownership). + std::vector> m_children; + + /// @brief Whether this node requires exclusive access via DataNodeAccessor. + const bool m_exclusiveAccess; + + /// @brief Log prefix prepended to all diagnostic messages emitted by this class. + static constexpr std::string_view LOG_PREFIX = "[DATA_NODE] "; +}; + +} // namespace score::crypto::daemon::data_manager + +#endif // CRYPTO_DAEMON_DATA_MANAGER_DATA_NODE_HPP_ diff --git a/score/crypto/daemon/data_manager/data_node_accessor.hpp b/score/crypto/daemon/data_manager/data_node_accessor.hpp new file mode 100644 index 0000000..3bd995a --- /dev/null +++ b/score/crypto/daemon/data_manager/data_node_accessor.hpp @@ -0,0 +1,216 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_DAEMON_DATA_MANAGER_DATA_NODE_ACCESSOR_HPP_ +#define CRYPTO_DAEMON_DATA_MANAGER_DATA_NODE_ACCESSOR_HPP_ + +#include +#include +#include +#include + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" + +namespace score::crypto::daemon::data_manager +{ + +/** + * @brief RAII scoped accessor for a DataNode that optionally enforces exclusive access. + * + * DataNodeAccessor wraps a shared pointer to a DataNode and keeps a back-pointer + * to the IDataManager that created it. On destruction the accessor automatically + * calls IDataManager::releaseNodeAccessor(), clearing the busy mark so that another + * caller may obtain access to the same node. + * + * @par Move semantics + * DataNodeAccessor is move-only. The move constructor and move-assignment operator + * transfer ownership and null the source's manager pointer, preventing a double + * release when the source is destroyed. + * + * @par Downcasting + * Use the consuming overload downCast() to obtain a + * @c DataNodeAccessor when the concrete type is known. The original + * accessor is invalidated by the call. + * + * @tparam T Concrete type of the managed node. Must derive from DataNode. + */ +template +class DataNodeAccessor +{ + static_assert(std::is_base_of::value, "T must be classes derived from DataNode."); + + public: + /** + * @brief Constructs an accessor owning @p node and registered with @p manager. + * + * @param node Shared pointer to the node being accessed. + * @param manager Pointer to the IDataManager that will be notified on destruction. + * Pass @c nullptr for non-exclusive nodes. + */ + DataNodeAccessor(std::shared_ptr node, const IDataManager* manager) : m_node(std::move(node)), m_manager(manager) + { + } + + /** + * @brief Releases the node back to the manager. + * + * Calls IDataManager::releaseNodeAccessor() if both the node and manager + * pointers are valid (i.e. this accessor was not moved-from). + */ + ~DataNodeAccessor() + { + if (m_node && m_manager) + { + if (!m_manager->releaseNodeAccessor(m_node->getClientId(), m_node->getNodeId()).has_value()) + { + std::cerr << LOG_PREFIX << "Failed to release node (" << m_node->getClientId() << ", " + << m_node->getNodeId() << ") in destructor\n"; + } + } + } + + /// @brief Copy construction is disabled; DataNodeAccessor is move-only. + DataNodeAccessor(const DataNodeAccessor&) = delete; + + /// @brief Copy assignment is disabled; DataNodeAccessor is move-only. + DataNodeAccessor& operator=(const DataNodeAccessor&) = delete; + + /** + * @brief Move constructor. + * + * Transfers ownership of the node and manager pointer from @p other. + * After the move, @p other's manager pointer is set to @c nullptr so that its + * destructor does not release the node. + * + * @param other Source accessor to move from. + */ + DataNodeAccessor(DataNodeAccessor&& other) noexcept : m_node(std::move(other.m_node)), m_manager(other.m_manager) + { + // Explicitly null manager ptr + other.m_manager = nullptr; + } + + /** + * @brief Move assignment operator. + * + * Releases the currently held node (if any), then transfers ownership from @p other. + * + * @param other Source accessor to move from. + */ + DataNodeAccessor& operator=(DataNodeAccessor&& other) noexcept + { + if (this == &other) + { + return *this; + } + + if (m_node && m_manager) + { + if (!m_manager->releaseNodeAccessor(m_node->getClientId(), m_node->getNodeId()).has_value()) + { + std::cerr << LOG_PREFIX << "Failed to release node (" << m_node->getClientId() << ", " + << m_node->getNodeId() << ") in move assignment operator\n"; + } + } + + m_node = std::move(other.m_node); + m_manager = other.m_manager; + // Explicitly null manager ptr + other.m_manager = nullptr; + + return *this; + } + + /** + * @brief Member-access operator (mutable). + * @return Pointer to the managed node. + */ + T* operator->() + { + return m_node.get(); + } + + /** + * @brief Dereference operator (mutable). + * @return Reference to the managed node. + */ + T& operator*() + { + return *m_node; + } + + /** + * @brief Member-access operator (const). + * @return Const pointer to the managed node. + */ + const T* operator->() const + { + return m_node.get(); + } + + /** + * @brief Dereference operator (const). + * @return Const reference to the managed node. + */ + const T& operator*() const + { + return *m_node; + } + + /** + * @brief Consuming downcast to a more-derived accessor type. + * + * Attempts to @c dynamic_pointer_cast the internal node to @c Derived. On + * success the current accessor is invalidated (its node and manager pointers + * are reset) and a new @c DataNodeAccessor is returned that takes + * over responsibility for releasing the node. + * + * @tparam Derived Target type, must derive from @c T. + * @return A @c DataNodeAccessor on success, or + * @c score::crypto::daemon::common::DaemonErrorCode::kInvalidDataType) if the cast fails. + */ + template + Expected, score::crypto::daemon::common::DaemonErrorCode> downCast() && + { + auto derived = std::dynamic_pointer_cast(m_node); + if (derived == nullptr) + { + std::cerr << LOG_PREFIX << "Could not convert to derived type\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidDataType); + } + + // This downcast is consuming the original object. Thus need to invalidate it. + m_node.reset(); + + const auto* manager = m_manager; + m_manager = nullptr; + + return DataNodeAccessor{std::move(derived), manager}; + } + + private: + /// @brief Shared pointer to the managed node. + std::shared_ptr m_node; + + /// @brief Non-owning pointer to the manager; null for non-exclusive nodes or after a move. + const IDataManager* m_manager; + + /// @brief Log prefix prepended to all diagnostic messages emitted by this class. + static constexpr std::string_view LOG_PREFIX = "[DATA_NODE_ACCESSOR] "; +}; + +} // namespace score::crypto::daemon::data_manager + +#endif // CRYPTO_DAEMON_DATA_MANAGER_DATA_NODE_ACCESSOR_HPP_ diff --git a/score/crypto/daemon/data_manager/docs/index.rst b/score/crypto/daemon/data_manager/docs/index.rst new file mode 100644 index 0000000..a2aae0d --- /dev/null +++ b/score/crypto/daemon/data_manager/docs/index.rst @@ -0,0 +1,124 @@ +Data Manager +============ + +Overview +-------- + +The Data Manager component provides a centralized store for managing "data +nodes" used by the crypto daemon. A data node can hold any data needed across multiple requests +such as the state associated with an ongoing cryptographic operation (e.g. a hash context +or cipher session). The components in the daemon's request processing chain insert and +access these nodes through the Data Manager. The Data Manager is responsible for storing, +lookup, lifecycle management, and safe concurrent access to those nodes. + +Responsibilities +---------------- + +- Provide a single registry for data nodes, indexed by both ClientId and DataNodeId. +- Control lifetime and ownership semantics for nodes. +- Ensure thread-safe access to nodes across the daemon using mutex-based synchronization + for all public operations. +- Offer efficient O(1) lookup while preventing resource leaks through proper RAII patterns. +- Manage hierarchical parent-child relationships between nodes and ensure cascading + cleanup when a parent is deleted. +- Enforce exclusive access semantics for nodes marked with ``exclusiveAccess=true`` via + a busy-node tracking mechanism. + +Design Decisions +---------------- + +.. dec_rec:: Central Data Node Manager + :id: dec_rec__crypto__central_data_node_manager + :status: accepted + :context: doc__crypto_architecture + :decision: Extract state information into a central Data Manager component. + + .. :affects: comp__crypto + +State information for ongoing daemon operations is stored in a dedicated, central +Data Manager component rather than being managed locally by individual handlers in the +request processing chain. + +Context +******* + +The crypto daemon needs to maintain state for ongoing operations (e.g. hash +contexts, cipher sessions). Clients reference this state via opaque DataNodeId values passed +through IPC, and different handlers in the request processing chain need to look up the +associated state by ID. Without a centralized facility, each handler would need to +independently manage node storage, lifecycle, and concurrency, which may result in duplicated logic, +inconsistent cleanup, and race conditions. + +Consequences +************ + +**Positive:** + +* Single point of responsibility for node creation, lookup, and deletion, avoiding + duplicated state-management logic across daemon components. +* Consistent lifecycle management through RAII patterns and + hierarchical parent-child relationships with cascading cleanup. +* Thread-safe access enforced uniformly via mutex-based synchronization within the + Data Manager, rather than each consumer implementing its own locking. +* Efficient O(1) node lookup through a two-level index (ClientId → DataNodeId → node). +* Exclusive-access semantics via a busy-node tracking mechanism and the DataNodeAccessor + RAII guard, preventing concurrent mutation of in-use nodes. +* Clean separation of concerns: handlers interact through the IDataManager interface + and DataNodeAccessor without knowing storage or concurrency internals. +* Hierarchical cleanup ensures that when a node is removed, all of its + children (e.g. crypto operation contexts) are automatically cleaned up, preventing + orphaned resources. +* Decouples state from thread affinity: since all node state is held centrally, any + worker thread can pick up a task and retrieve the associated state from the Data + Manager, making it straightforward to distribute or reassign work across threads. + +**Negative:** + +* The central Data Manager introduces a single mutex that serializes all node operations, + which may become a bottleneck under high contention. +* All handlers in the request processing chain become dependent on the Data Manager + interface, increasing coupling to this central component. + +.. note:: + In case the mutex contention becomes a bottleneck, potential improvements could be analyzed + such as splitting the mutex per ClientId to allow more concurrent access + while still maintaining thread safety. + +Alternatives Considered +*********************** + +Distributed State Management per Component +""""""""""""""""""""""""""""""""""""""""""" + +Each handler in the request processing chain manages its own set of data nodes +independently using local data structures and per-handler synchronization. + +Advantages +"""""""""" + +* No single contention point — each component locks only its own state, allowing + independent scalability. +* Components remain self-contained with no shared dependency on a central manager. + +Disadvantages +""""""""""""" + +* Duplicated storage and lifecycle logic across multiple handlers, increasing + maintenance burden and risk of inconsistencies. +* No centralized hierarchical cleanup — each handler must independently track + parent-child relationships and handle cascading removal. +* Risk of inconsistent cleanup semantics across handlers may lead to resource leaks. +* Harder to enforce uniform concurrency guarantees and exclusive-access semantics. +* State is tied to the handler that created it, making it harder to redistribute + work across threads. + +Justification for the Decision +****************************** + +The crypto daemon's data nodes need to be accessed by different handlers in the request +processing chain, each looking up state by ID. Centralizing this state in a single Data +Manager eliminates duplicated storage and lifecycle logic, provides uniform thread safety +through a single synchronization strategy, and enables consistent hierarchical lifecycle +management with cascading cleanup. The serialization cost of a single mutex is acceptable +given the expected operation frequency, and the simplicity it provides outweighs the +theoretical contention risk. diff --git a/score/crypto/daemon/data_manager/i_data_manager.hpp b/score/crypto/daemon/data_manager/i_data_manager.hpp new file mode 100644 index 0000000..9eb77ca --- /dev/null +++ b/score/crypto/daemon/data_manager/i_data_manager.hpp @@ -0,0 +1,189 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_DAEMON_DATA_MANAGER_I_DATA_MANAGER_HPP_ +#define CRYPTO_DAEMON_DATA_MANAGER_I_DATA_MANAGER_HPP_ + +#include +#include +#include + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/data_manager/data_node.hpp" + +namespace score::crypto::daemon::data_manager +{ + +/// @brief Number of bits in a byte, used for bit-width assertions. +constexpr std::size_t BITS_PER_BYTE = 8; + +/// @brief Number of high-order bits in a DataNodeId reserved for future use (bits 56–63). +constexpr std::size_t DATA_NODE_ID_RESERVED_BITS = 8; + +/// @brief Number of usable value bits in a DataNodeId (bits 0–55). +constexpr std::size_t DATA_NODE_ID_VALUE_BITS = 56; + +static_assert(sizeof(DataNodeId) * BITS_PER_BYTE == DATA_NODE_ID_RESERVED_BITS + DATA_NODE_ID_VALUE_BITS, + "Error: The size of data_manager::DataNodeId and its components do not match."); + +/// @brief Maximum value representable in the 56-bit value field of a DataNodeId. +constexpr std::size_t DATA_NODE_VALUE_MAX = (1ULL << DATA_NODE_ID_VALUE_BITS) - 1; + +template +class DataNodeAccessor; + +/** + * @brief Generic interface for managing hierarchical data nodes with unique identification. + * + * The IDataManager is responsible for: + * - Creating data nodes (independent or with parent-child relationships). + * - Assigning unique IDs combining a client-provided identifier and a manager-assigned counter. + * - Providing exclusive or non-exclusive access to nodes via DataNodeAccessor. + * - Deleting nodes and cascading cleanup to all descendants. + * - Maintaining hierarchical parent-child relationships. + * + * @par Use case + * In the crypto daemon, when a connection is established: + * - @c ClientId represents the Application ID or Session ID (family identifier). + * - Each crypto operation creates data nodes associated with that @c ClientId. + * - The manager assigns a unique counter per @c ClientId family. + * - When the connection closes, all nodes for that @c ClientId are cleaned up via + * deleteClientNodes(). + * + * @par Thread safety + * Derived implementations must document their own thread-safety guarantees. + */ +class IDataManager +{ + public: + using Sptr = std::shared_ptr; + + IDataManager() = default; + IDataManager(const IDataManager&) = delete; + IDataManager& operator=(const IDataManager&) = delete; + IDataManager(IDataManager&&) = delete; + IDataManager& operator=(IDataManager&&) = delete; + + virtual ~IDataManager() = default; + + /** + * @brief Registers a new root-level node for the given client. + * + * The manager assigns a unique @c DataNodeId and stores the node as an entry + * (top-level) node under @p clientId. + * + * @param clientId Identifier of the owning client. + * @param node Shared pointer to the node to register. Must not be null. + * @return The assigned @c DataNodeId on success, or an @c score::crypto::daemon::common::DaemonErrorCode + * on failure. + */ + virtual Expected addNode( + ClientId clientId, + std::shared_ptr node) = 0; + + /** + * @brief Registers a new child node under an existing parent node. + * + * The manager assigns a unique @c DataNodeId, links the new node to @p parentId, + * and stores it in the node map for @p clientId. + * + * @param clientId Identifier of the owning client. + * @param parentId @c DataNodeId of the existing parent node. + * @param node Shared pointer to the node to register. Must not be null. + * @return The assigned @c DataNodeId on success, or an @c score::crypto::daemon::common::DaemonErrorCode + * on failure. + */ + virtual Expected + addChildNode(ClientId clientId, DataNodeId parentId, std::shared_ptr node) = 0; + + /** + * @brief Registers a new sibling node under the same parent as an existing node. + * + * The manager finds the parent of @p siblingId, assigns a unique @c DataNodeId, + * links the new node to the same parent, and stores it in the node map for @p clientId. + * + * @param clientId Identifier of the owning client. + * @param siblingId @c DataNodeId of the existing sibling node whose parent will be used. + * @param node Shared pointer to the node to register. Must not be null. + * @return The assigned @c DataNodeId on success, or an @c ErrorType on failure + * (including @c ERROR_INVALID_CONTEXT if the sibling has no parent). + */ + virtual Expected + addSiblingNode(ClientId clientId, DataNodeId siblingId, std::shared_ptr node) = 0; + + /** + * @brief Deletes a node and all of its descendants for the given client. + * + * Performs a depth-first traversal starting at @p nodeId and removes every + * node in the subtree from storage and the reference lookup. If the deleted node is a root-level entry + * node it is also removed from the entry-node index. + * + * @param clientId Identifier of the owning client. + * @param nodeId @c DataNodeId of the root node to delete. + * @return @c std::monostate on success, or an @c score::crypto::daemon::common::DaemonErrorCode on + * failure. + */ + virtual Expected deleteNode(ClientId clientId, + DataNodeId nodeId) = 0; + + /** + * @brief Deletes all nodes that belong to the given client. + * + * Iterates over every root-level entry node registered for @p clientId and + * recursively removes the whole subtree beneath each one. + * + * @param clientId Identifier of the client whose nodes should be removed. + * @return @c std::monostate on success, or an @c score::crypto::daemon::common::DaemonErrorCode if any + * removal fails. The first encountered error is preserved; remaining nodes are still processed. + */ + virtual Expected deleteClientNodes( + ClientId clientId) = 0; + + /** + * @brief Releases the exclusive-access mark on a node held by a DataNodeAccessor. + * + * Called automatically by the DataNodeAccessor destructor. Should not normally + * be invoked directly by application code. + * + * @param clientId Identifier of the owning client. + * @param nodeId @c DataNodeId of the node whose busy mark should be cleared. + * @return @c std::monostate on success, or an @c score::crypto::daemon::common::DaemonErrorCode on + * failure. + */ + virtual Expected releaseNodeAccessor( + ClientId clientId, + DataNodeId nodeId) const = 0; + + /** + * @brief Obtains a scoped accessor to a node, optionally enforcing exclusive access. + * + * If the node requires exclusive access (DataNode::requiresExclusiveAccess() == true), + * this method marks the node as busy. The busy mark is cleared when the returned + * DataNodeAccessor is destroyed. Attempting to obtain a second accessor for an + * exclusively-accessed node while it is busy returns @c + * score::crypto::daemon::common::DaemonErrorCode::kResourceBusy). + * + * @param clientId Identifier of the owning client. + * @param nodeId @c DataNodeId of the node to access. + * @return A DataNodeAccessor wrapping the node on success, or an @c + * score::crypto::daemon::common::DaemonErrorCode on failure (including @c ERROR_RESOURCE_BUSY if already + * exclusively held). + */ + virtual Expected, score::crypto::daemon::common::DaemonErrorCode> getNodeAccessor( + ClientId clientId, + DataNodeId nodeId) const = 0; +}; + +} // namespace score::crypto::daemon::data_manager + +#endif // CRYPTO_DAEMON_DATA_MANAGER_I_DATA_MANAGER_HPP_ diff --git a/score/crypto/daemon/data_manager/src/data_manager.cpp b/score/crypto/daemon/data_manager/src/data_manager.cpp new file mode 100644 index 0000000..09885fd --- /dev/null +++ b/score/crypto/daemon/data_manager/src/data_manager.cpp @@ -0,0 +1,529 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/data_manager/data_manager.hpp" +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/data_manager/data_node_accessor.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" +#include "score/crypto/daemon/data_manager/src/data_node_manager_token.hpp" + +namespace score::crypto::daemon::data_manager +{ + +Expected DataManager::addNode( + ClientId clientId, + std::shared_ptr node) +{ + std::cout << LOG_PREFIX << "addNode for clientId: " << clientId << "\n"; + + if (!node) + { + std::cerr << LOG_PREFIX << "addNode: Invalid node sptr" + << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + const std::lock_guard lock(m_mutex); + + auto nodeIdRes = getNextNodeIdLocked(clientId, lock); + if (!nodeIdRes) + { + std::cerr << LOG_PREFIX << "addNode: Could not get a nodeId" + << "\n"; + return make_unexpected(nodeIdRes.error()); + } + auto nodeId = nodeIdRes.value(); + + // Only DataManager may mutate these fields; pass access token + node->setNodeId(nodeId, DataNodeManagerToken{}); + node->setClientId(clientId, DataNodeManagerToken{}); + + m_node_map.try_emplace(clientId).first->second.emplace(nodeId, node); + m_entry_nodes_per_client.try_emplace(clientId).first->second.push_back(nodeId); + + std::cout << LOG_PREFIX << "addNode new nodeId: " << nodeId << "\n"; + + return nodeId; +} + +Expected +DataManager::addChildNode(ClientId clientId, DataNodeId parentId, std::shared_ptr node) +{ + std::cout << LOG_PREFIX << "addChildNode for clientId: " << clientId << " with parentId: " << parentId << "\n"; + + if (!node) + { + std::cerr << LOG_PREFIX << "addChildNode: Invalid node sptr" + << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + const std::lock_guard lock(m_mutex); + + auto parentRes = getNodeLocked(clientId, parentId, lock); + if (!parentRes.has_value()) + { + std::cerr << LOG_PREFIX << "addChildNode: Failed to get parent" + << "\n"; + return make_unexpected(parentRes.error()); + } + const auto& parent = parentRes.value(); + + auto nodeIdRes = getNextNodeIdLocked(clientId, lock); + if (!nodeIdRes) + { + std::cerr << LOG_PREFIX << "addChildNode: Could not get a nodeId" + << "\n"; + return make_unexpected(nodeIdRes.error()); + } + auto nodeId = nodeIdRes.value(); + + // Only DataManager may mutate these fields; pass access token + node->setNodeId(nodeId, DataNodeManagerToken{}); + node->setClientId(clientId, DataNodeManagerToken{}); + + parent->addChild(node, DataNodeManagerToken{}); + node->setParent(parent, DataNodeManagerToken{}); + + // Since we have parent with the same clientId, the client entry does already exist + m_node_map.at(clientId).emplace(nodeId, node); + + std::cout << LOG_PREFIX << "addChildNode new nodeId: " << nodeId << "\n"; + + return nodeId; +} + +Expected +DataManager::addSiblingNode(ClientId clientId, DataNodeId siblingId, std::shared_ptr node) +{ + std::cout << LOG_PREFIX << "addSiblingNode for clientId: " << clientId << " with siblingId: " << siblingId << "\n"; + + if (!node) + { + std::cerr << LOG_PREFIX << "addSiblingNode: Invalid node sptr" + << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + const std::lock_guard lock(m_mutex); + + auto siblingRes = getNodeLocked(clientId, siblingId, lock); + if (!siblingRes.has_value()) + { + std::cerr << LOG_PREFIX << "addSiblingNode: Failed to get sibling" + << "\n"; + return make_unexpected(siblingRes.error()); + } + const auto& sibling = siblingRes.value(); + + auto parentIdRes = sibling->getParent(DataNodeManagerToken{}); + if (!parentIdRes.has_value()) + { + std::cerr << LOG_PREFIX << "addSiblingNode: Sibling has no parent" + << "\n"; + return make_unexpected(parentIdRes.error()); + } + const auto parentId = parentIdRes.value(); + + auto parentRes = getNodeLocked(clientId, parentId, lock); + if (!parentRes.has_value()) + { + std::cerr << LOG_PREFIX << "addSiblingNode: Failed to get parent" + << "\n"; + return make_unexpected(parentRes.error()); + } + const auto& parent = parentRes.value(); + + auto nodeIdRes = getNextNodeIdLocked(clientId, lock); + if (!nodeIdRes) + { + std::cerr << LOG_PREFIX << "addSiblingNode: Could not get a nodeId" + << "\n"; + return make_unexpected(nodeIdRes.error()); + } + auto nodeId = nodeIdRes.value(); + + // Only DataManager may mutate these fields; pass access token + node->setNodeId(nodeId, DataNodeManagerToken{}); + node->setClientId(clientId, DataNodeManagerToken{}); + + parent->addChild(node, DataNodeManagerToken{}); + node->setParent(parent, DataNodeManagerToken{}); + + // Since we have parent with the same clientId, the client entry does already exist + m_node_map.at(clientId).emplace(nodeId, node); + + std::cout << LOG_PREFIX << "addSiblingNode new nodeId: " << nodeId << "\n"; + + return nodeId; +} + +Expected DataManager::deleteClientNodes( + ClientId clientId) +{ + std::cout << LOG_PREFIX << "deleteClientNodes for clientId: " << clientId << "\n"; + + const std::lock_guard lock(m_mutex); + + Expected result; + + auto clientItr = m_entry_nodes_per_client.find(clientId); + if (clientItr == m_entry_nodes_per_client.end()) + { + std::cerr << LOG_PREFIX << "deleteClientNodes: Unknown clientId: " << clientId << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + // Work on a copy, since the original will be modified during the removal + auto clientEntryNodes = clientItr->second; + for (auto entryNodeId : clientEntryNodes) + { + auto subResult = removeNodesFromStorage(clientId, entryNodeId, lock); + if (!subResult.has_value() && result.has_value()) + { + result = make_unexpected(subResult.error()); + } + } + + return result; +} + +Expected DataManager::deleteNode(ClientId clientId, + DataNodeId nodeId) +{ + std::cout << LOG_PREFIX << "deleteNode for clientId: " << clientId << " nodeId: " << nodeId << "\n"; + + const std::lock_guard lock(m_mutex); + auto result = removeNodesFromStorage(clientId, nodeId, lock); + + return result; +} + +Expected +DataManager::removeNodesFromStorage(ClientId clientId, DataNodeId rootNodeId, const std::lock_guard& _lock) +{ + std::cout << LOG_PREFIX << "removeNodesFromStorage with clientId:" << clientId << " nodeId: " << rootNodeId << "\n"; + + Expected result{}; + + // Single traversal: collect nodes in visit order together with their shared_ptr so we + // do not look them up a second time during the removal phase. + std::vector stack; + std::vector>> postOrder; + stack.push_back(rootNodeId); + + while (!stack.empty()) + { + const DataNodeId currentId = stack.back(); + stack.pop_back(); + + auto nodeRes = getNodeLocked(clientId, currentId, _lock); + if (!nodeRes.has_value()) + { + std::cerr << LOG_PREFIX << "removeNodesFromStorage: getNodeLocked failed for node " << currentId << "\n"; + if (result.has_value()) + { + result = make_unexpected(nodeRes.error()); + } + continue; + } + + auto node = std::move(nodeRes).value(); + + for (const auto& childId : node->getChildren(DataNodeManagerToken{})) + { + stack.push_back(childId); + } + + postOrder.emplace_back(currentId, std::move(node)); + } + + for (auto it = postOrder.rbegin(); it != postOrder.rend(); ++it) + { + const DataNodeId currentId = it->first; + const auto& node = it->second; + + auto subResult = removeSingleNodeFromStorage(node, clientId, currentId, _lock); + if (!subResult.has_value() && result.has_value()) + { + std::cerr << LOG_PREFIX << "removeNodesFromStorage: removeSingleNodeFromStorage failed for node " + << currentId << "\n"; + result = make_unexpected(subResult.error()); + } + } + + return result; +} + +Expected DataManager::removeSingleNodeFromStorage( + const std::shared_ptr& node, + ClientId clientId, + DataNodeId nodeId, + const std::lock_guard& _lock) +{ + std::cout << LOG_PREFIX << "removeSingleNodeFromStorage with clientId:" << clientId << " nodeId: " << nodeId + << "\n"; + + Expected result{}; + + auto parentId = node->getParent(DataNodeManagerToken{}); + if (parentId.has_value()) + { + // Remove the current node from its parent to ensure it is deleted first. + auto parentRes = getNodeLocked(clientId, parentId.value(), _lock); + if (parentRes.has_value()) + { + result = parentRes.value()->removeChild(nodeId, DataNodeManagerToken{}); + } + else + { + std::cerr << LOG_PREFIX << "removeNodesFromStorage: getNodeLocked of parent failed " << parentId.value() + << "\n"; + result = make_unexpected(parentRes.error()); + } + } + else + { + result = removeEntryNodeFromIndex(clientId, nodeId, _lock); + } + + auto nodeMapItr = m_node_map.find(clientId); + if (nodeMapItr != m_node_map.end()) + { + nodeMapItr->second.erase(nodeId); + + // Remove the whole element if empty + if (nodeMapItr->second.empty()) + { + m_node_map.erase(nodeMapItr); + } + } + else + { + std::cerr << LOG_PREFIX << "removeSingleNodeFromStorage: Not found: clientId: " << clientId << "\n"; + if (result.has_value()) + { + result = make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + } + // This should not be necessary as we do not expect the user to retrieve an node accessor + // and remove the same node. But to be safe, also remove it from the busy nodes. + auto busyNodesItr = m_busy_nodes.find(clientId); + if (busyNodesItr != m_busy_nodes.end()) + { + busyNodesItr->second.erase(nodeId); + + // Remove the whole element if empty + if (busyNodesItr->second.empty()) + { + m_busy_nodes.erase(busyNodesItr); + } + } + + return result; +} + +Expected DataManager::removeEntryNodeFromIndex( + ClientId clientId, + DataNodeId nodeId, + const std::lock_guard& /*_lock*/) +{ + std::cout << LOG_PREFIX << "removeEntryNodeFromIndex: ClientId:" << clientId << " nodeId: " << nodeId << "\n"; + auto clientItr = m_entry_nodes_per_client.find(clientId); + if (clientItr == m_entry_nodes_per_client.end()) + { + std::cerr << LOG_PREFIX << "removeEntryNodeFromIndex: Not found: clientId: " << clientId << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + auto& client = clientItr->second; + + auto nodeItr = std::find(client.begin(), client.end(), nodeId); + if (nodeItr != client.end()) + { + client.erase(nodeItr); + } + else + { + std::cerr << LOG_PREFIX << "removeEntryNodeFromIndex: Not found: nodeId: " << nodeId << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + // Remove the whole element if empty + if (client.empty()) + { + m_entry_nodes_per_client.erase(clientItr); + } + + return {}; +} + +Expected, score::crypto::daemon::common::DaemonErrorCode> +DataManager::getNodeLocked(ClientId clientId, DataNodeId nodeId, const std::lock_guard& /*_lock*/) const +{ + std::cout << LOG_PREFIX << "getNodeLocked: ClientId:" << clientId << " nodeId: " << nodeId << "\n"; + + auto clientItr = m_node_map.find(clientId); + if (clientItr == m_node_map.end()) + { + std::cerr << LOG_PREFIX << "getNodeLocked: Not found: clientId: " << clientId << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + const auto& client = clientItr->second; + + auto node = client.find(nodeId); + if (node == client.end()) + { + std::cerr << LOG_PREFIX << "getNodeLocked: Not found: nodeId: " << nodeId << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + return node->second; +} + +Expected DataManager::getNextNodeIdLocked( + ClientId clientId, + const std::lock_guard& /*_lock*/) +{ + DataNodeId nodeId = 0; + + // TODO: Check if implications of N retires is ok in terms of runtime. + // TODO: This could be configurable. Current value is a gut decision. + // Retry a fixed number of times to find a free ID + // Number of retires is based on the maximum number of nodes per client (+1 to find the next free one) + auto retriesLeft = m_max_nodes_per_client + 1; + + // Ensure the counter entry exists for this client, initialised to 0 if new + auto [counterIt, _unused] = m_id_counter_per_client.try_emplace(clientId, 1); + + auto clientNodesItr = m_node_map.find(clientId); + if (clientNodesItr == m_node_map.end()) + { + // No nodes for this client yet, so the first ID (1) is available + return counterIt->second; + } + + while (retriesLeft > 0) + { + nodeId = counterIt->second; + + // Check whether this ID is already in use + if (clientNodesItr->second.find(nodeId) == clientNodesItr->second.end()) + { + return nodeId; + } + + // Increment counter; wrap around to 1 when exceeding the maximum + counterIt->second += 1; + if (static_cast(counterIt->second) > DATA_NODE_VALUE_MAX) + { + counterIt->second = 1; + } + + retriesLeft--; + } + + std::cerr << LOG_PREFIX << "getNextNodeIdLocked: Not free nodeId found" + << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kQuotaExceeded); +} + +Expected DataManager::releaseNodeAccessor( + ClientId clientId, + DataNodeId nodeId) const +{ + std::cout << LOG_PREFIX << "releaseAccessor for clientId: " << clientId << " with nodeId: " << nodeId << "\n"; + + const std::lock_guard lock(m_mutex); + + auto clientItr = m_busy_nodes.find(clientId); + if (clientItr == m_busy_nodes.end()) + { + std::cerr << LOG_PREFIX << "releaseAccessor: Failed to get client" + << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + auto& client = clientItr->second; + + auto nodeItr = client.find(nodeId); + if (nodeItr == client.end()) + { + std::cerr << LOG_PREFIX << "releaseAccessor: Failed to get node" + << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + clientItr->second.erase(nodeId); + + // Remove the whole element if empty + if (clientItr->second.empty()) + { + m_busy_nodes.erase(clientItr); + } + + return {}; +} + +Expected, score::crypto::daemon::common::DaemonErrorCode> DataManager::getNodeAccessor( + ClientId clientId, + DataNodeId nodeId) const +{ + std::cout << LOG_PREFIX << "getNodeAccessor for clientId: " << clientId << " with nodeId: " << nodeId << "\n"; + + const std::lock_guard lock(m_mutex); + + auto nodeRes = getNodeLocked(clientId, nodeId, lock); + if (!nodeRes.has_value()) + { + std::cerr << LOG_PREFIX << "getNodeAccessor: Failed to get node" + << "\n"; + return make_unexpected(nodeRes.error()); + } + auto node = std::move(nodeRes).value(); + if (!node->requiresExclusiveAccess()) + { + // Non-exclusive, we do not need to keep track of the object, + // thus no need to capture the manager + return DataNodeAccessor(std::move(node), nullptr); + } + + auto clientItr = m_busy_nodes.find(clientId); + if (clientItr != m_busy_nodes.end()) + { + auto& client = clientItr->second; + auto nodeItr = client.find(nodeId); + + if (nodeItr != client.end()) + { + std::cerr << LOG_PREFIX << "getNodeAccessor: Node is currently busy" + << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kResourceBusy); + } + } + + // Mark busy + m_busy_nodes.try_emplace(clientId).first->second.insert(nodeId); + + return DataNodeAccessor(std::move(node), this); +} + +} // namespace score::crypto::daemon::data_manager diff --git a/score/crypto/daemon/data_manager/src/data_node.cpp b/score/crypto/daemon/data_manager/src/data_node.cpp new file mode 100644 index 0000000..272dec3 --- /dev/null +++ b/score/crypto/daemon/data_manager/src/data_node.cpp @@ -0,0 +1,116 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include +#include +#include +#include +#include + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/data_manager/data_node.hpp" + +namespace score::crypto::daemon::data_manager +{ + +// Default constructor uses default initialization for members +// DataNodeId is initialized by DataManager via SetNodeId() + +DataNode::DataNode(bool exclusiveAccess) : m_exclusiveAccess(exclusiveAccess) {} + +DataNodeId DataNode::getNodeId() const +{ + return m_nodeId; +} + +void DataNode::setNodeId(DataNodeId nodeId, const DataNodeManagerToken& /*token*/) +{ + m_nodeId = nodeId; +} + +void DataNode::setParent(const std::weak_ptr& parent, const DataNodeManagerToken& /*token*/) +{ + // Since we expect the DataNodeManagerToken, DataManager is responsible for thread safety + m_parent = parent; +} + +ClientId DataNode::getClientId() const +{ + return m_client_id; +} + +void DataNode::setClientId(ClientId clientId, const DataNodeManagerToken& /*token*/) +{ + m_client_id = clientId; +} + +Expected DataNode::getParent( + const DataNodeManagerToken& /*token*/) const +{ + // Since we expect the DataNodeManagerToken, DataManager is responsible for thread safety + auto parent = m_parent.lock(); + if (!parent) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidContext); + } + + return parent->getNodeId(); +} + +void DataNode::addChild(const std::shared_ptr& child, const DataNodeManagerToken& /*token*/) +{ + // Since we expect the DataNodeManagerToken, DataManager is responsible for thread safety + m_children.push_back(child); +} + +std::vector DataNode::getChildren(const DataNodeManagerToken& /*token*/) const +{ + // Since we expect the DataNodeManagerToken, DataManager is responsible for thread safety + std::vector child_ids; + child_ids.reserve(m_children.size()); + for (const auto& child : m_children) + { + child_ids.push_back(child->getNodeId()); + } + + return child_ids; +} + +Expected DataNode::removeChild( + DataNodeId nodeId, + const DataNodeManagerToken& /*token*/) +{ + // Since we expect the DataNodeManagerToken, DataManager is responsible for thread safety + auto child = std::find_if(m_children.begin(), m_children.end(), [nodeId](const DataNode::Sptr& child_itr) { + return nodeId == child_itr->getNodeId(); + }); + if (child != m_children.end()) + { + m_children.erase(child); + } + else + { + std::cerr << LOG_PREFIX << "removeChild could not find child with nodeId: " << nodeId << "\n"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + return {}; +} + +bool DataNode::requiresExclusiveAccess() const +{ + // m_exclusiveAccess is const, no need to lock mutex + return m_exclusiveAccess; +} + +} // namespace score::crypto::daemon::data_manager diff --git a/score/crypto/daemon/data_manager/src/data_node_manager_token.hpp b/score/crypto/daemon/data_manager/src/data_node_manager_token.hpp new file mode 100644 index 0000000..76a71ad --- /dev/null +++ b/score/crypto/daemon/data_manager/src/data_node_manager_token.hpp @@ -0,0 +1,41 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_DAEMON_DATA_MANAGER_DATA_NODE_MANAGER_TOKEN_HPP_ +#define CRYPTO_DAEMON_DATA_MANAGER_DATA_NODE_MANAGER_TOKEN_HPP_ + +namespace score::crypto::daemon::data_manager +{ + +class DataManager; // forward-declare + +/** + * @brief Capability token restricting mutation of DataNode fields to the DataManager. + * + * All DataNode APIs that modify internal state (e.g. setNodeId(), setClientId(), + * setParent(), addChild(), removeChild()) require a @c DataNodeManagerToken to be + * passed by the caller. + * + * The constructor is private and only @c DataManager is declared as a friend, + * so only the DataManager can create tokens. This prevents arbitrary code from + * directly mutating a node's identity or topology outside of the manager. + */ +class DataNodeManagerToken +{ + private: + DataNodeManagerToken() = default; + friend class DataManager; +}; + +} // namespace score::crypto::daemon::data_manager + +#endif // CRYPTO_DAEMON_DATA_MANAGER_DATA_NODE_MANAGER_TOKEN_HPP_ diff --git a/score/crypto/daemon/key_management/BUILD b/score/crypto/daemon/key_management/BUILD new file mode 100644 index 0000000..2945f94 --- /dev/null +++ b/score/crypto/daemon/key_management/BUILD @@ -0,0 +1,117 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +# Header-only library: operation constants only (no key management infra deps) +cc_library( + name = "key_management_operations", + hdrs = ["interfaces/key_management_operations.hpp"], + visibility = ["//:__subpackages__"], + deps = ["//score/crypto/daemon/common"], +) + +# Minimal leaf target: IKeyHandler + key_types only. +# No dep on handler_headers — breaks the handler_headers <-> key_management_headers cycle. +# Both handler_headers and key_management_headers depend on this target. +cc_library( + name = "key_handler_iface", + hdrs = [ + "interfaces/i_key_handler.hpp", + "interfaces/key_types.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/common:common_types", + "//score/crypto/daemon/common", + "//score/mw/crypto/api/common:crypto_common", + "//score/mw/crypto/api/config:context_configs", + ], +) + +# Header-only library: interfaces, config types, operations, guards, data nodes +cc_library( + name = "key_management_headers", + hdrs = [ + # interfaces/ + "interfaces/i_key_factory.hpp", + "interfaces/i_key_slot_catalog.hpp", + "interfaces/i_key_slot_handler.hpp", + "interfaces/key_management_operations.hpp", + "interfaces/key_slot_config.hpp", + # slot/ + "slot/access_policy_enforcer.hpp", + "slot/config_driven_slot_catalog.hpp", + "slot/deployment_loader.hpp", + "slot/deployment_writer.hpp", + "slot/file_backed_slot_handler.hpp", + "slot/slot_registry.hpp", + # nodes/ + "nodes/key_data_node.hpp", + "nodes/key_slot_data_node.hpp", + # core/ + "core/key_entry.hpp", + "core/key_management_service.hpp", + "core/key_registry.hpp", + # detail/ + "detail/slot_info_builder.hpp", + # root/ + "key_management_module.hpp", + ], + includes = ["."], + visibility = [ + "//:__subpackages__", + ], + deps = [ + ":key_handler_iface", + "//score/crypto/daemon/common", + "//score/crypto/daemon/control_plane:request_handler_hdr", + "//score/crypto/daemon/data_manager", + "//score/crypto/daemon/provider/handler:handler_headers", + "//score/mw/crypto/api/common:crypto_common", + "//score/mw/crypto/api/config:context_configs", + ], +) + +# Implementation library: compiled sources +cc_library( + name = "key_management", + srcs = [ + # interfaces/ (default implementations for virtual methods) + "interfaces/i_key_factory.cpp", + "interfaces/i_key_slot_handler.cpp", + # slot/ + "slot/access_policy_enforcer.cpp", + "slot/config_driven_slot_catalog.cpp", + "slot/deployment_loader.cpp", + "slot/deployment_writer.cpp", + "slot/file_backed_slot_handler.cpp", + "slot/slot_registry.cpp", + # core/ + "core/key_management_service.cpp", + "core/key_registry.cpp", + # root/ + "key_management_module.cpp", + ], + includes = ["."], + linkstatic = True, + visibility = ["//:__subpackages__"], + deps = [ + ":key_management_headers", + "//score/crypto/daemon/common", + "//score/crypto/daemon/data_manager", + "//score/crypto/daemon/key_management/slot/deployment:kv_deployment", + "//score/crypto/daemon/provider:provider_headers", + "//score/crypto/daemon/provider:provider_manager", + "//score/mw/crypto/api/common:crypto_common", + ], +) diff --git a/score/crypto/daemon/key_management/core/key_entry.hpp b/score/crypto/daemon/key_management/core/key_entry.hpp new file mode 100644 index 0000000..b28c9ea --- /dev/null +++ b/score/crypto/daemon/key_management/core/key_entry.hpp @@ -0,0 +1,127 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_CORE_KEY_ENTRY_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_CORE_KEY_ENTRY_HPP + +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_handler.hpp" +#include "score/crypto/daemon/key_management/slot/slot_registry.hpp" +#include "score/mw/crypto/api/common/types.hpp" + +#include +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::key_management +{ + +/// Registry entry for a single live key (ephemeral or slot-loaded). +/// +/// Takes exclusive ownership of an IKeyHandler. The destructor calls +/// IKeyHandler::Release() to securely zeroize and free key material. +/// +/// Client-visible references are KeyDataNode instances (formerly KeyDataNode) +/// in the DataManager client tree. +/// +/// Inherits enable_shared_from_this for ContextDataNode co-ownership: a +/// ContextDataNode may hold a shared_ptr to prevent a dangling +/// ProviderKeyHandle reference if the user releases their context early. +class KeyEntry final : public std::enable_shared_from_this +{ + public: + /// @param handler Owning key handler (must not be nullptr). + /// @param provider_id Numeric provider ID (uint16_t) that created this key. + /// @param slot_handle Non-default only when key was loaded from a slot. + KeyEntry(IKeyHandler::Sptr handler, common::ProviderId provider_id, SlotHandle slot_handle = SlotHandle{}) + : m_handler{std::move(handler)}, m_provider_id{provider_id}, m_slot_handle{slot_handle} + { + } + + /// Releases key material. + ~KeyEntry() + { + if (m_handler) + { + static_cast(m_handler->Release()); + } + } + + KeyEntry(const KeyEntry&) = delete; + KeyEntry& operator=(const KeyEntry&) = delete; + KeyEntry(KeyEntry&&) = delete; + KeyEntry& operator=(KeyEntry&&) = delete; + + /// Read the opaque provider key handle metadata. + [[nodiscard]] const ProviderKeyHandle& GetHandle() const noexcept + { + return m_handler->GetHandle(); + } + + /// Access the key handler (e.g., for crypto operations that need raw bytes). + [[nodiscard]] IKeyHandler::Sptr GetKeyHandler() const noexcept + { + return m_handler; + } + + [[nodiscard]] const common::ProviderId GetProviderId() const noexcept + { + return m_provider_id; + } + + // ----------------------------------------------------------------------- + // Reference counting for shared key access across clients + // ----------------------------------------------------------------------- + + /// Record an additional client holding a reference to this key. + void AddRef(data_manager::ClientId client_id) + { + const std::lock_guard lock(m_ref_mutex); + m_ref_count.fetch_add(1U, std::memory_order_relaxed); + m_referencing_clients.push_back(client_id); + } + + /// Remove a client reference. + /// + /// @return true when the reference count has reached zero. + /// The caller must then unregister this key from the KeyRegistry. + bool Release(data_manager::ClientId client_id) + { + const std::lock_guard lock(m_ref_mutex); + m_referencing_clients.erase(std::remove(m_referencing_clients.begin(), m_referencing_clients.end(), client_id), + m_referencing_clients.end()); + const std::uint32_t prev = m_ref_count.fetch_sub(1U, std::memory_order_acq_rel); + return prev == 1U; + } + + /// Current number of live references. + [[nodiscard]] std::uint32_t GetRefCount() const noexcept + { + return m_ref_count.load(std::memory_order_acquire); + } + + private: + IKeyHandler::Sptr m_handler; + common::ProviderId m_provider_id; + SlotHandle m_slot_handle; + + mutable std::mutex m_ref_mutex; ///< Protects m_referencing_clients. + std::atomic m_ref_count{0U}; + std::vector m_referencing_clients; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_CORE_KEY_ENTRY_HPP diff --git a/score/crypto/daemon/key_management/core/key_management_service.cpp b/score/crypto/daemon/key_management/core/key_management_service.cpp new file mode 100644 index 0000000..873794f --- /dev/null +++ b/score/crypto/daemon/key_management/core/key_management_service.cpp @@ -0,0 +1,594 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/core/key_management_service.hpp" + +#include "score/crypto/daemon/data_manager/data_node_accessor.hpp" +#include "score/crypto/daemon/key_management/core/key_entry.hpp" +#include "score/crypto/daemon/key_management/nodes/key_data_node.hpp" +#include "score/crypto/daemon/key_management/nodes/key_slot_data_node.hpp" + +#include +#include + +namespace score::crypto::daemon::key_management +{ +namespace +{ +constexpr std::string_view LOG_PREFIX = "[KM_SERVICE] "; +} // namespace + +// --------------------------------------------------------------------------- +// Construction +// --------------------------------------------------------------------------- + +KeyManagementService::KeyManagementService(data_manager::IDataManager::Sptr data_manager, + provider::ProviderManager::Sptr provider_manager, + SlotRegistry::Sptr slot_registry) + : m_data_manager{std::move(data_manager)}, + m_provider_manager{std::move(provider_manager)}, + m_slot_registry{std::move(slot_registry)} +{ + if (!m_data_manager || !m_provider_manager || !m_slot_registry) + { + std::cerr << LOG_PREFIX << "constructor: null dependency injected\n"; + } +} + +// --------------------------------------------------------------------------- +// RegisterKeyMaterial +// --------------------------------------------------------------------------- + +Expected KeyManagementService::RegisterKeyMaterial( + const KeyRegistrationParams& params, + IKeyHandler::Sptr handler) +{ + if (!handler) + { + std::cerr << LOG_PREFIX << "RegisterKeyMaterial: null handler\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + auto key_node = std::make_shared(std::move(handler), params.provider_id, params.slot_handle); + + auto& registry = GetProviderRegistry(params.provider_id); + + KeyRegistryId reg_id{}; + if (params.slot_handle.IsValid()) + { + reg_id = registry.RegisterSlotKey(params.slot_handle, key_node); + if (reg_id == 0U) + { + // Race condition: another thread registered this slot between our LoadOrShare() + // check and this registration attempt. Our loaded key_node will be destroyed + // (and handler released) when we replace it below. + reg_id = registry.FindSlotRegistryId(params.slot_handle); + if (reg_id == 0U) + { + std::cerr << LOG_PREFIX << "RegisterKeyMaterial: race condition - slot registered but ID not found\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + // Look up the key node that won the race. + key_node = registry.FindById(reg_id); + if (!key_node) + { + std::cerr << LOG_PREFIX << "RegisterKeyMaterial: existing key not found after race condition\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + } + } + else + { + reg_id = registry.RegisterEphemeralKey(key_node); + } + + return CreateKeyDataNode(params.client_id, params.parent_id, std::move(key_node), reg_id, params.provider_id); +} + +// --------------------------------------------------------------------------- +// LoadOrShare +// --------------------------------------------------------------------------- + +Expected KeyManagementService::LoadOrShare( + const KeyRegistrationParams& params, + IKeySlotHandler& slot_handler, + const KeySlotConfig& slot_config) +{ + auto& registry = GetProviderRegistry(params.provider_id); + + // Check if this slot is already loaded in the registry. + auto existing = registry.FindBySlot(params.slot_handle); + if (existing != nullptr) + { + // Reuse the existing KeyDataNode — add a reference for this client. + // We need the registry ID for the existing entry. Look it up via FindBySlot + // indirectly: we know the entry exists, so retrieve its ID through the + // slot_to_id mapping by calling FindSlotRegistryId. + const auto reg_id = registry.FindSlotRegistryId(params.slot_handle); + return CreateKeyDataNode(params.client_id, params.parent_id, std::move(existing), reg_id, params.provider_id); + } + + // Not yet loaded — call the provider's slot handler. + auto load_result = slot_handler.LoadKey(slot_config); + if (!load_result.has_value()) + { + // LoadKey failed. Could be due to another thread loading the slot simultaneously + // (e.g., PKCS#11 rejects duplicate loads). Check if it was registered in the meantime. + auto existing = registry.FindBySlot(params.slot_handle); + if (existing != nullptr) + { + const auto reg_id = registry.FindSlotRegistryId(params.slot_handle); + return CreateKeyDataNode( + params.client_id, params.parent_id, std::move(existing), reg_id, params.provider_id); + } + + std::cerr << LOG_PREFIX << "LoadOrShare: LoadKey failed and slot not in registry\n"; + return score::crypto::make_unexpected(load_result.error()); + } + + return RegisterKeyMaterial(params, std::move(load_result.value())); +} + +// --------------------------------------------------------------------------- +// ReleaseKeyMaterial +// --------------------------------------------------------------------------- + +Expected KeyManagementService::ReleaseKeyMaterial( + data_manager::ClientId client_id, + data_manager::DataNodeId ref_node_id) +{ + // Deleting the node from the DataManager triggers ~KeyDataNode which + // calls Release() on the shared KeyDataNode and, if it was the last + // reference, invokes the unregister callback to remove the key from the + // KeyRegistry. + auto del_result = m_data_manager->deleteNode(client_id, ref_node_id); + if (!del_result.has_value()) + { + std::cerr << LOG_PREFIX << "ReleaseKeyMaterial: deleteNode failed\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + + return std::monostate{}; +} + +// --------------------------------------------------------------------------- +// CleanupClient +// --------------------------------------------------------------------------- + +void KeyManagementService::CleanupClient(data_manager::ClientId client_id) +{ + m_slot_node_cache.erase(client_id); + for (auto& [provider_id, registry] : m_registries) + { + static_cast(provider_id); + registry.CleanupClient(client_id); + } +} + +// --------------------------------------------------------------------------- +// GetProviderRegistry +// --------------------------------------------------------------------------- + +KeyRegistry& KeyManagementService::GetProviderRegistry(const common::ProviderId& provider_id) +{ + return m_registries[provider_id]; +} + +// --------------------------------------------------------------------------- +// CreateKeyDataNode +// --------------------------------------------------------------------------- + +Expected KeyManagementService::CreateKeyDataNode( + data_manager::ClientId client_id, + data_manager::DataNodeId parent_id, + std::shared_ptr key_node, + KeyRegistryId registry_id, + const common::ProviderId& provider_id) +{ + auto& registry = GetProviderRegistry(provider_id); + + auto captured_key_node = key_node; // keep a copy before move + + auto ref_node = + std::make_shared(std::move(key_node), registry_id, client_id, [®istry](KeyRegistryId id) { + registry.Unregister(id); + }); + + auto node_id_result = m_data_manager->addChildNode(client_id, parent_id, ref_node); + if (!node_id_result.has_value()) + { + std::cerr << LOG_PREFIX << "CreateKeyDataNode: addChildNode failed\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + return KeyDataNodeResult{node_id_result.value(), std::move(captured_key_node)}; +} + +// --------------------------------------------------------------------------- +// ResolveKeySlot +// --------------------------------------------------------------------------- + +Expected KeyManagementService::ResolveKeySlot( + const std::string& resource_name, + data_manager::ClientId client_id) +{ + // Dedup: return cached DataNodeId when the same client resolves the same resource twice. + auto& client_cache = m_slot_node_cache[client_id]; + const auto cache_it = client_cache.find(resource_name); + if (cache_it != client_cache.end()) + { + return cache_it->second; + } + + // Resolve via SlotRegistry (performs access-policy check). + auto handle_result = m_slot_registry->ResolveAppResource(resource_name, client_id); + if (!handle_result.has_value()) + { + std::cerr << LOG_PREFIX << "ResolveKeySlot: SlotRegistry::ResolveAppResource failed for '" << resource_name + << "'\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + + // Create a lightweight KeySlotDataNode and register it in the DataManager. + auto slot_node = std::make_shared(handle_result.value(), m_slot_registry); + auto node_id_result = m_data_manager->addNode(client_id, std::move(slot_node)); + if (!node_id_result.has_value()) + { + std::cerr << LOG_PREFIX << "ResolveKeySlot: addNode failed\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + client_cache[resource_name] = node_id_result.value(); + return node_id_result.value(); +} + +// --------------------------------------------------------------------------- +// ResolveSlotForOperation +// --------------------------------------------------------------------------- + +Expected KeyManagementService::ResolveSlotForOperation( + data_manager::ClientId client_id, + data_manager::DataNodeId slot_node_id) +{ + auto accessor_res = m_data_manager->getNodeAccessor(client_id, slot_node_id); + if (!accessor_res.has_value()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + + auto slot_acc = std::move(accessor_res.value()).downCast(); + if (!slot_acc.has_value()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + + const auto& slot_node = *slot_acc.value(); + const auto slot_handle = slot_node.GetSlotHandle(); + auto slot_cfg_res = slot_node.GetConfig(); + if (!slot_cfg_res.has_value()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + + return SlotResolution{slot_cfg_res.value(), slot_handle}; +} + +// --------------------------------------------------------------------------- +// ResolveKeyForOperation +// --------------------------------------------------------------------------- + +Expected +KeyManagementService::ResolveKeyForOperation(data_manager::ClientId client_id, + data_manager::DataNodeId key_node_id) const +{ + auto accessor_res = m_data_manager->getNodeAccessor(client_id, key_node_id); + if (!accessor_res.has_value()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + + auto ref_acc = std::move(accessor_res.value()).downCast(); + if (!ref_acc.has_value()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + + auto key_node = ref_acc.value()->GetKeyEntry(); + if (!key_node) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + auto handler = key_node->GetKeyHandler(); + if (!handler) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + return handler; +} + +// --------------------------------------------------------------------------- +// BindKeyToContext +// --------------------------------------------------------------------------- + +Expected KeyManagementService::BindKeyToContext( + data_manager::ClientId client_id, + data_manager::DataNodeId context_node_id, + data_manager::DataNodeId key_node_id, + const common::ProviderId& target_provider_id) +{ + // ------------------------------------------------------------------ + // Single accessor lookup + GetNodeType() dispatch. + // + // Follows the same pattern as ResolveTargetProvider: read the node + // type tag first, then downCast inside the matching branch. This + // avoids the previous double-getNodeAccessor + trial-downcast pattern. + // ------------------------------------------------------------------ + auto node_accessor = m_data_manager->getNodeAccessor(client_id, key_node_id); + if (!node_accessor.has_value()) + { + std::cerr << LOG_PREFIX << "BindKeyToContext: node lookup failed for key_node_id=" << key_node_id << "\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + const auto node_type = node_accessor.value()->GetNodeType(); + + // ------------------------------------------------------------------ + // Slot-direct path (KeySlotDataNode). + // + // Resolve the slot's primary provider, obtain its IKeySlotHandler, + // and delegate to LoadOrShare which handles HW deduplication. + // The resulting KeyDataNode is parented under context_node_id so + // it is cascade-deleted when the context is closed. + // ------------------------------------------------------------------ + if (node_type == data_manager::DataNodeType::kKeySlot) + { + auto slot_acc = std::move(node_accessor.value()).downCast(); + if (!slot_acc.has_value()) + { + std::cerr << LOG_PREFIX << "BindKeyToContext: kKeySlot downCast failed (internal)\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + auto* slot_node = &(*slot_acc.value()); + auto slot_config_res = slot_node->GetConfig(); + if (!slot_config_res.has_value()) + { + std::cerr << LOG_PREFIX + << "BindKeyToContext: KeySlotDataNode has no config " + "(key_node_id=" + << key_node_id << ")\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + const auto* slot_config = slot_config_res.value(); + const auto slot_handle = slot_node->GetSlotHandle(); + + // Determine the primary provider for this slot. + auto primary_prov_res = m_slot_registry->GetPrimaryProviderId(slot_handle); + if (!primary_prov_res.has_value()) + { + std::cerr << LOG_PREFIX << "BindKeyToContext: no primary provider for slot " << key_node_id << "\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + const common::ProviderId& slot_provider_id = primary_prov_res.value(); + + // Cross-provider guard: key's provider must match the target context provider. + if (target_provider_id != common::kInvalidProviderId && slot_provider_id != target_provider_id) + { + std::cerr << LOG_PREFIX << "BindKeyToContext: cross-provider binding not supported " + << "(key provider=" << slot_provider_id << ", target provider=" << target_provider_id << ")\n"; + return score::crypto::make_unexpected( + score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); + } + + auto slot_provider = m_provider_manager->GetProvider(slot_provider_id); + if (!slot_provider) + { + std::cerr << LOG_PREFIX << "BindKeyToContext: provider '" << slot_provider_id << "' not found\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + auto slot_handler = slot_provider->GetKeySlotHandler(*slot_config); + if (!slot_handler) + { + std::cerr << LOG_PREFIX << "BindKeyToContext: provider '" << slot_provider_id + << "' does not support slot operations\n"; + return score::crypto::make_unexpected( + score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); + } + + // Load (or reuse) key material; ref node parented under context. + auto ref_result = + LoadOrShare({client_id, context_node_id, slot_provider_id, slot_handle}, *slot_handler, *slot_config); + if (!ref_result.has_value()) + { + std::cerr << LOG_PREFIX << "BindKeyToContext: LoadOrShare failed for " << key_node_id << "\n"; + return score::crypto::make_unexpected(ref_result.error()); + } + + // KeyDataNodeResult carries the KeyEntry — no re-lookup needed. + return KeyBindingResult{ + ref_result.value().key_node->GetKeyHandler(), + ref_result.value().node_id, + }; + } + + // ------------------------------------------------------------------ + // Loaded-key path (KeyDataNode). + // + // Create a NEW KeyDataNode child under context_node_id so the + // context owns its own reference. The user's original ref stays + // independent — releasing it will not affect the context's binding. + // ------------------------------------------------------------------ + if (node_type == data_manager::DataNodeType::kKeyData) + { + auto ref_acc = std::move(node_accessor.value()).downCast(); + if (!ref_acc.has_value()) + { + std::cerr << LOG_PREFIX << "BindKeyToContext: kKeyData downCast failed (internal)\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + auto key_data_node = ref_acc.value()->GetKeyEntry(); + if (!key_data_node) + { + std::cerr << LOG_PREFIX + << "BindKeyToContext: KeyDataNode has no backing " + "KeyDataNode\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + // Cross-provider guard: key's provider must match the target context provider. + const auto& key_provider_id = key_data_node->GetProviderId(); + if (target_provider_id != common::kInvalidProviderId && key_provider_id != target_provider_id) + { + std::cerr << LOG_PREFIX << "BindKeyToContext: cross-provider binding not supported " + << "(key provider=" << key_provider_id << ", target provider=" << target_provider_id << ")\n"; + return score::crypto::make_unexpected( + score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); + } + + const auto registry_id = ref_acc.value()->GetRegistryId(); + + auto new_ref_result = + CreateKeyDataNode(client_id, context_node_id, key_data_node, registry_id, key_provider_id); + if (!new_ref_result.has_value()) + { + std::cerr << LOG_PREFIX + << "BindKeyToContext: failed to create context-owned " + "KeyDataNode for loaded key\n"; + return score::crypto::make_unexpected(new_ref_result.error()); + } + + return KeyBindingResult{ + key_data_node->GetKeyHandler(), + new_ref_result.value().node_id, + }; + } + + // Neither kKeySlot nor kKeyData invalid argument. + std::cerr << LOG_PREFIX << "BindKeyToContext: key_node_id=" << key_node_id + << " is neither a KeySlotDataNode nor a KeyDataNode" + << " (node_type=" << static_cast(node_type) << ")\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); +} + +// --------------------------------------------------------------------------- +// Provider resolution for keyed contexts +// --------------------------------------------------------------------------- + +Expected +KeyManagementService::ResolveTargetProvider(data_manager::ClientId client_id, + common::CryptoProviderType requested_type, + std::optional key_node_id) +{ + // No key binding ? resolve purely from provider type. + if (!key_node_id.has_value()) + { + auto provider = m_provider_manager->GetProvider(requested_type); + if (!provider) + { + std::cerr << LOG_PREFIX << "ResolveTargetProvider: no provider for type " + << static_cast(requested_type) << "\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + return provider->GetProviderId(); + } + + // Key supplied — resolve node type and extract provider affinity. + auto node_accessor = m_data_manager->getNodeAccessor(client_id, key_node_id.value()); + if (!node_accessor.has_value()) + { + std::cerr << LOG_PREFIX << "ResolveTargetProvider: node lookup failed for key_node_id=" << key_node_id.value() + << "\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + const auto node_type = node_accessor.value()->GetNodeType(); + + // ------- KeySlotDataNode (slot-direct path) ------- + if (node_type == data_manager::DataNodeType::kKeySlot) + { + auto slot_acc = std::move(node_accessor.value()).downCast(); + if (!slot_acc.has_value()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + auto config_res = slot_acc.value()->GetConfig(); + if (!config_res.has_value() || config_res.value()->provider_ids.empty()) + { + std::cerr << LOG_PREFIX << "ResolveTargetProvider: slot has no config or providers\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + const auto* config = config_res.value(); + + if (requested_type == common::CryptoProviderType::DEFAULT) + { + return config->GetPrimaryProviderId(); + } + + // Filter slot's provider list by type compatibility (primary first). + for (const auto& pid : config->provider_ids) + { + if (m_provider_manager->IsProviderCompatibleWithType(pid, requested_type)) + { + return pid; + } + } + + std::cerr << LOG_PREFIX << "ResolveTargetProvider: no compatible provider for slot '" << config->slot_name + << "' with type " << static_cast(requested_type) << "\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kIncompatibleKeyType); + } + + // ------- KeyDataNode (loaded-key path) ------- + if (node_type == data_manager::DataNodeType::kKeyData) + { + auto ref_acc = std::move(node_accessor.value()).downCast(); + if (!ref_acc.has_value()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + auto key_data_node = ref_acc.value()->GetKeyEntry(); + if (!key_data_node) + { + std::cerr << LOG_PREFIX + << "ResolveTargetProvider: KeyDataNode has no " + "backing KeyDataNode\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + const auto& key_provider_id = key_data_node->GetProviderId(); + + if (requested_type == common::CryptoProviderType::DEFAULT) + { + return key_provider_id; + } + + if (m_provider_manager->IsProviderCompatibleWithType(key_provider_id, requested_type)) + { + return key_provider_id; + } + + std::cerr << LOG_PREFIX << "ResolveTargetProvider: key's provider '" << key_provider_id + << "' incompatible with type " << static_cast(requested_type) << "\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kIncompatibleKeyType); + } + + std::cerr << LOG_PREFIX << "ResolveTargetProvider: key_node_id=" << key_node_id.value() + << " is neither a KeySlotDataNode nor a KeyDataNode\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/core/key_management_service.hpp b/score/crypto/daemon/key_management/core/key_management_service.hpp new file mode 100644 index 0000000..70013d6 --- /dev/null +++ b/score/crypto/daemon/key_management/core/key_management_service.hpp @@ -0,0 +1,262 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_CORE_KEY_MANAGEMENT_SERVICE_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_CORE_KEY_MANAGEMENT_SERVICE_HPP + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" +#include "score/crypto/daemon/key_management/core/key_registry.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_factory.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_handler.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_types.hpp" +#include "score/crypto/daemon/key_management/slot/slot_registry.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" + +#include +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::key_management +{ + +/// Outcome of a successful slot resolution. +struct SlotResolution +{ + const KeySlotConfig* config; ///< Points to config in SlotRegistry (valid for operation duration). + SlotHandle handle; ///< Lightweight token for the resolved slot. +}; + +/// Outcome of a key-to-context binding. +/// +/// Returned by BindKeyToContext so the mediator can pass the IKeyHandler +/// to the crypto handler via InitializationParams.bound_key_handler. +struct KeyBindingResult +{ + IKeyHandler::Sptr key_handler; ///< Bound key handler for the provider. + data_manager::DataNodeId resolved_node_id; ///< KeyDataNode id (may differ from + ///< input when slot-direct path was used). +}; + +/// Internal result of CreateKeyDataNode / RegisterKeyMaterial / LoadOrShare. +/// +/// Carries both the new DataNodeId and the backing KeyEntry so that +/// callers avoid a redundant DataManager re-lookup (see BindKeyToContext). +struct KeyDataNodeResult +{ + data_manager::DataNodeId node_id; + std::shared_ptr key_node; +}; + +/// Core orchestration service for key management operations. +/// +/// Provides primitives consumed by provider executors for DataNode lifecycle +/// management, slot resolution, and context-level key binding (single-provider only). +/// +/// Provider-specific crypto operations (GenerateKey, DeriveKey, LoadKey, etc.) +/// are NOT part of this service. Executors call IKeyFactory and IKeySlotHandler +/// directly for those, then use the service for bookkeeping integration. +/// +/// Thread safety: methods are serialized by the underlying DataManager and +/// SlotRegistry mutexes. The service itself holds no mutable state beyond +/// the injected shared pointers. +class KeyManagementService final +{ + public: + using Sptr = std::shared_ptr; + + KeyManagementService(data_manager::IDataManager::Sptr data_manager, + provider::ProviderManager::Sptr provider_manager, + SlotRegistry::Sptr slot_registry); + + ~KeyManagementService() = default; + + KeyManagementService(const KeyManagementService&) = delete; + KeyManagementService& operator=(const KeyManagementService&) = delete; + KeyManagementService(KeyManagementService&&) = delete; + KeyManagementService& operator=(KeyManagementService&&) = delete; + + // ------------------------------------------------------------------ + // DataNode lifecycle + // ------------------------------------------------------------------ + + /// Register a newly created key in the provider's KeyRegistry and place + /// a KeyDataNode in the client's tree under parent_id. + /// + /// Ownership of the IKeyHandler is transferred to the KeyDataNode which + /// lives in the registry. The returned DataNodeId identifies the + /// KeyDataNode (the guard) in the client's DataManager tree. + /// + /// @param params Client, parent, provider, and optional slot identity. + /// @param handler Key handler produced by IKeyFactory or IKeySlotHandler. + /// @return DataNodeId of the KeyDataNode on success. + [[nodiscard]] Expected RegisterKeyMaterial( + const KeyRegistrationParams& params, + IKeyHandler::Sptr handler); + + /// Load a key from a slot, reusing an existing registry entry when the + /// provider already holds the key (HW deduplication). + /// + /// If the slot is already loaded in the provider's registry, a new + /// KeyDataNode is created referencing the existing KeyDataNode. + /// Otherwise, slot_handler.LoadKey() is called to produce a fresh + /// IKeyHandler which is then registered. + /// + /// @param params Client, parent, provider, and slot identity. + /// @param slot_handler Provider slot handler for LoadKey(). + /// @param slot_config Resolved slot configuration. + /// @return DataNodeId of the KeyDataNode on success. + [[nodiscard]] Expected + LoadOrShare(const KeyRegistrationParams& params, IKeySlotHandler& slot_handler, const KeySlotConfig& slot_config); + + /// Release a client's reference to a key. + /// + /// Looks up the KeyDataNode by ref_node_id, removes it from the + /// DataManager tree, and decrements the KeyDataNode's reference count. + /// When the last reference is dropped, the key is unregistered from + /// the KeyRegistry and its destructor securely zeroizes key material. + [[nodiscard]] Expected ReleaseKeyMaterial( + data_manager::ClientId client_id, + data_manager::DataNodeId ref_node_id); + + /// Remove all key references for a disconnected or crashed client. + /// + /// Called as a safety net after DataManager::deleteClientNodes(). + void CleanupClient(data_manager::ClientId client_id); + + // ------------------------------------------------------------------ + // Context-level key binding + // ------------------------------------------------------------------ + + /// Bind a key (loaded or slot) to a context at creation time. + /// + /// Accepts either a KeySlotDataNode ID (slot-direct path) or a + /// KeyDataNode ID (loaded-key path). For the slot-direct path the + /// service internally loads the key via LoadOrShare and parents the + /// resulting KeyDataNode under @p context_node_id so it is released + /// automatically when the context is closed. + /// + /// @param client_id Owning client. + /// @param context_node_id Context the key is bound to. + /// @param key_node_id DataNodeId of a KeySlotDataNode or KeyDataNode. + /// @param target_provider_id Provider that owns the context; cross-provider + /// binding returns kNotSupported. + /// @return KeyBindingResult on success; DaemonErrorCode on failure. + [[nodiscard]] Expected BindKeyToContext( + data_manager::ClientId client_id, + data_manager::DataNodeId context_node_id, + data_manager::DataNodeId key_node_id, + const common::ProviderId& target_provider_id); + + // ------------------------------------------------------------------ + // Provider resolution for keyed contexts + // ------------------------------------------------------------------ + + /// Resolve the target provider for a keyed context creation. + /// + /// Considers the key/slot's provider affinity and the caller's requested + /// provider type to determine which provider should create the handler. + /// + /// Resolution precedence: + /// - No key ? GetProvider(requested_type) + /// - Key + DEFAULT type ? primary provider of key/slot + /// - Key + specific type ? first compatible provider from key/slot's + /// provider list; hard fail if none match. + /// + /// @param client_id Client requesting the context. + /// @param requested_type Provider type preference (DEFAULT = any). + /// @param key_node_id DataNodeId of a KeySlotDataNode or KeyDataNode, + /// or std::nullopt when no key is involved. + /// @return ProviderId on success; DaemonErrorCode on failure. + [[nodiscard]] Expected ResolveTargetProvider( + data_manager::ClientId client_id, + common::CryptoProviderType requested_type, + std::optional key_node_id = std::nullopt); + + // ------------------------------------------------------------------ + // Slot orchestration + // ------------------------------------------------------------------ + + /// Resolve an application resource name to a session-scoped KeySlotDataNode. + /// + /// Performs SlotRegistry::ResolveAppResource(), creates a KeySlotDataNode if + /// no node for this (client, resource) pair exists yet, and returns the + /// DataNodeId to the caller. Repeated calls with the same arguments return + /// the same DataNodeId (deduplication — fixes Point 5). + [[nodiscard]] Expected ResolveKeySlot( + const std::string& resource_name, + data_manager::ClientId client_id); + + /// Resolve a slot DataNode ID to its configuration and handle. + [[nodiscard]] Expected ResolveSlotForOperation( + data_manager::ClientId client_id, + data_manager::DataNodeId slot_node_id); + + /// Resolve a key DataNode ID (a KeyDataNode) back to its IKeyHandler. + /// + /// Used by executor handlers that need a ProviderKeyHandle or the raw + /// IKeyHandler to build provider-level request structs (WrapKey, ExportKey, ...). + [[nodiscard]] Expected ResolveKeyForOperation( + data_manager::ClientId client_id, + data_manager::DataNodeId key_node_id) const; + + // ------------------------------------------------------------------ + // Accessors + // ------------------------------------------------------------------ + + [[nodiscard]] data_manager::IDataManager::Sptr GetDataManager() const noexcept + { + return m_data_manager; + } + + [[nodiscard]] SlotRegistry::Sptr GetSlotRegistry() const noexcept + { + return m_slot_registry; + } + + [[nodiscard]] provider::ProviderManager::Sptr GetProviderManager() const noexcept + { + return m_provider_manager; + } + + /// Retrieve (or create) the KeyRegistry for a given provider. + [[nodiscard]] KeyRegistry& GetProviderRegistry(const common::ProviderId& provider_id); + + private: + /// Create a KeyDataNode in the client tree pointing at the given + /// KeyDataNode from the registry. + [[nodiscard]] Expected CreateKeyDataNode( + data_manager::ClientId client_id, + data_manager::DataNodeId parent_id, + std::shared_ptr key_node, + KeyRegistryId registry_id, + const common::ProviderId& provider_id); + + data_manager::IDataManager::Sptr m_data_manager; + provider::ProviderManager::Sptr m_provider_manager; + SlotRegistry::Sptr m_slot_registry; + std::unordered_map m_registries; + + /// Per-client cache of resolved resource names → DataNodeId. + /// Cleared in CleanupClient(). Prevents duplicate KeySlotDataNodes. + std::unordered_map> + m_slot_node_cache; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_CORE_KEY_MANAGEMENT_SERVICE_HPP diff --git a/score/crypto/daemon/key_management/core/key_registry.cpp b/score/crypto/daemon/key_management/core/key_registry.cpp new file mode 100644 index 0000000..1dbbf6e --- /dev/null +++ b/score/crypto/daemon/key_management/core/key_registry.cpp @@ -0,0 +1,186 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/core/key_registry.hpp" + +#include "score/crypto/daemon/key_management/core/key_entry.hpp" + +#include +#include +#include + +namespace score::crypto::daemon::key_management +{ +namespace +{ +constexpr std::string_view LOG_PREFIX = "[KEY_REGISTRY] "; +} // namespace + +// --------------------------------------------------------------------------- +// Registration +// --------------------------------------------------------------------------- + +KeyRegistryId KeyRegistry::RegisterSlotKey(SlotHandle slot_handle, std::shared_ptr key_node) +{ + const std::lock_guard lock(m_mutex); + + // Dedup check — caller should have called FindBySlot() first, but guard + // against concurrent races. + if (m_slot_to_id.count(slot_handle.index) != 0U) + { + std::cerr << LOG_PREFIX << "RegisterSlotKey: slot " << slot_handle.index << " already registered\n"; + return 0U; + } + + const KeyRegistryId id = m_next_id++; + m_keys.emplace(id, std::move(key_node)); + m_slot_to_id.emplace(slot_handle.index, id); + + return id; +} + +KeyRegistryId KeyRegistry::RegisterEphemeralKey(std::shared_ptr key_node) +{ + const std::lock_guard lock(m_mutex); + + const KeyRegistryId id = m_next_id++; + m_keys.emplace(id, std::move(key_node)); + + return id; +} + +// --------------------------------------------------------------------------- +// Lookup +// --------------------------------------------------------------------------- + +std::shared_ptr KeyRegistry::FindBySlot(SlotHandle slot_handle) const +{ + const std::lock_guard lock(m_mutex); + + const auto slot_it = m_slot_to_id.find(slot_handle.index); + if (slot_it == m_slot_to_id.end()) + { + return nullptr; + } + + const auto key_it = m_keys.find(slot_it->second); + if (key_it == m_keys.end()) + { + return nullptr; + } + + return key_it->second; +} + +KeyRegistryId KeyRegistry::FindSlotRegistryId(SlotHandle slot_handle) const +{ + const std::lock_guard lock(m_mutex); + + const auto slot_it = m_slot_to_id.find(slot_handle.index); + if (slot_it == m_slot_to_id.end()) + { + return 0U; + } + + return slot_it->second; +} + +std::shared_ptr KeyRegistry::FindById(KeyRegistryId id) const +{ + const std::lock_guard lock(m_mutex); + + const auto it = m_keys.find(id); + if (it == m_keys.end()) + { + return nullptr; + } + + return it->second; +} + +// --------------------------------------------------------------------------- +// Removal +// --------------------------------------------------------------------------- + +bool KeyRegistry::Unregister(KeyRegistryId id) +{ + const std::lock_guard lock(m_mutex); + + const auto it = m_keys.find(id); + if (it == m_keys.end()) + { + return false; + } + + // Remove reverse mapping if this was a slot-loaded key. + for (auto slot_it = m_slot_to_id.begin(); slot_it != m_slot_to_id.end(); ++slot_it) + { + if (slot_it->second == id) + { + m_slot_to_id.erase(slot_it); + break; + } + } + + m_keys.erase(it); + return true; +} + +// --------------------------------------------------------------------------- +// Crash cleanup +// --------------------------------------------------------------------------- + +void KeyRegistry::CleanupClient(data_manager::ClientId client_id) +{ + const std::lock_guard lock(m_mutex); + + // Collect IDs of keys to remove (cannot mutate m_keys while iterating). + std::vector to_remove; + + for (auto& [id, key_node] : m_keys) + { + if (key_node->Release(client_id)) + { + // ref_count reached zero — mark for removal. + to_remove.push_back(id); + } + } + + for (const auto id : to_remove) + { + // Remove reverse slot mapping. + for (auto slot_it = m_slot_to_id.begin(); slot_it != m_slot_to_id.end(); ++slot_it) + { + if (slot_it->second == id) + { + m_slot_to_id.erase(slot_it); + break; + } + } + + std::cout << LOG_PREFIX << "CleanupClient: removing key " << id << " (ref_count reached 0 after client " + << client_id << " cleanup)\n"; + m_keys.erase(id); + } +} + +// --------------------------------------------------------------------------- +// Query +// --------------------------------------------------------------------------- + +std::size_t KeyRegistry::Size() const +{ + const std::lock_guard lock(m_mutex); + return m_keys.size(); +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/core/key_registry.hpp b/score/crypto/daemon/key_management/core/key_registry.hpp new file mode 100644 index 0000000..a89034b --- /dev/null +++ b/score/crypto/daemon/key_management/core/key_registry.hpp @@ -0,0 +1,140 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_CORE_KEY_REGISTRY_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_CORE_KEY_REGISTRY_HPP + +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/key_management/slot/slot_registry.hpp" + +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::key_management +{ + +// Forward declaration — full definition in key_data_node.hpp. +class KeyEntry; + +/// Unique identifier for a key within a KeyRegistry. +using KeyRegistryId = std::uint64_t; + +/// Per-provider registry of live keys. +/// +/// Holds shared ownership of every KeyEntry produced by a single +/// provider (both slot-loaded and ephemeral). KeyDataNode instances +/// in the client tree hold an additional shared_ptr to the same +/// KeyEntry, keeping it alive as long as any client references it. +/// +/// Thread safety: all public methods serialise on an internal mutex that +/// is independent of the DataManager lock. The lock hierarchy is: +/// DataManager::m_mutex → (release) → KeyRegistry::m_mutex +/// Callers must NOT hold the DataManager lock when calling into this class. +class KeyRegistry final +{ + public: + using Sptr = std::shared_ptr; + + KeyRegistry() = default; + ~KeyRegistry() = default; + + KeyRegistry(const KeyRegistry&) = delete; + KeyRegistry& operator=(const KeyRegistry&) = delete; + KeyRegistry(KeyRegistry&&) = delete; + KeyRegistry& operator=(KeyRegistry&&) = delete; + + // ------------------------------------------------------------------ + // Registration + // ------------------------------------------------------------------ + + /// Register a key that was loaded from a persistent slot. + /// + /// The slot_handle is used as a deduplication key: if a KeyEntry + /// for the same slot is already registered, this call fails with + /// kAlreadyExists (caller should use FindBySlot() first). + /// + /// @return Assigned KeyRegistryId on success. + [[nodiscard]] KeyRegistryId RegisterSlotKey(SlotHandle slot_handle, std::shared_ptr key_node); + + /// Register an ephemeral (non-slot) key. + /// + /// @return Assigned KeyRegistryId. + [[nodiscard]] KeyRegistryId RegisterEphemeralKey(std::shared_ptr key_node); + + // ------------------------------------------------------------------ + // Lookup + // ------------------------------------------------------------------ + + /// Find a previously registered slot-loaded key. + /// + /// @return shared_ptr to the KeyEntry, or nullptr if not found. + [[nodiscard]] std::shared_ptr FindBySlot(SlotHandle slot_handle) const; + + /// Find the registry ID for a slot-loaded key. + /// + /// @return KeyRegistryId, or 0 if not found. + [[nodiscard]] KeyRegistryId FindSlotRegistryId(SlotHandle slot_handle) const; + + /// Find a key by its registry-assigned ID. + /// + /// @return shared_ptr to the KeyEntry, or nullptr if not found. + [[nodiscard]] std::shared_ptr FindById(KeyRegistryId id) const; + + // ------------------------------------------------------------------ + // Removal + // ------------------------------------------------------------------ + + /// Remove a key from the registry. + /// + /// After this call, the registry no longer holds a shared_ptr to the + /// KeyEntry. If no other shared_ptrs exist (i.e. all KeyDataNodes + /// were already destroyed) the KeyEntry destructor runs immediately. + /// + /// @return true if the key was found and removed, false otherwise. + bool Unregister(KeyRegistryId id); + + // ------------------------------------------------------------------ + // Crash cleanup + // ------------------------------------------------------------------ + + /// Remove all references for a given client from every key in the + /// registry. Keys whose reference count drops to zero are unregistered. + /// + /// Called as a safety net after DataManager::deleteClientNodes(). + void CleanupClient(data_manager::ClientId client_id); + + // ------------------------------------------------------------------ + // Query + // ------------------------------------------------------------------ + + /// Number of keys currently held in the registry. + [[nodiscard]] std::size_t Size() const; + + private: + mutable std::mutex m_mutex; + + /// Master table: registry-assigned ID → KeyDataNode. + std::unordered_map> m_keys; + + /// Reverse map: slot handle index → registry ID (for slot-loaded dedup). + std::unordered_map m_slot_to_id; + + /// Monotonically increasing ID counter. + KeyRegistryId m_next_id{1U}; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_CORE_KEY_REGISTRY_HPP diff --git a/score/crypto/daemon/key_management/detail/slot_info_builder.hpp b/score/crypto/daemon/key_management/detail/slot_info_builder.hpp new file mode 100644 index 0000000..54da693 --- /dev/null +++ b/score/crypto/daemon/key_management/detail/slot_info_builder.hpp @@ -0,0 +1,31 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_DETAIL_SLOT_INFO_BUILDER_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_DETAIL_SLOT_INFO_BUILDER_HPP + +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" +#include "score/mw/crypto/api/common/types.hpp" + +namespace score::crypto::daemon::key_management::detail +{ + +inline score::mw::crypto::KeySlotInfo BuildKeySlotInfo(const KeySlotConfig& slot, + score::mw::crypto::KeySlotState state, + uint16_t primary_provider = 0U) noexcept +{ + score::mw::crypto::KeySlotInfo info{}; + info.state = state; + info.algorithm = (state == score::mw::crypto::KeySlotState::kOccupied) ? slot.algorithm : common::AlgorithmId{}; + info.primary_provider = primary_provider; + info.permitted_operations = slot.allowed_operations; + info.compatible_provider_count = 0U; + return info; +} + +} // namespace score::crypto::daemon::key_management::detail + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_DETAIL_SLOT_INFO_BUILDER_HPP diff --git a/score/crypto/daemon/key_management/interfaces/i_key_factory.cpp b/score/crypto/daemon/key_management/interfaces/i_key_factory.cpp new file mode 100644 index 0000000..1d0018e --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/i_key_factory.cpp @@ -0,0 +1,134 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/interfaces/i_key_factory.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp" + +#include "score/crypto/daemon/common/daemon_error.hpp" + +namespace score::crypto::daemon::key_management +{ + +score::crypto::Expected IKeyFactory::GenerateKey( + const KeyGenerationRequest& /*request*/) +{ + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +score::crypto::Expected IKeyFactory::ImportKey( + const KeyImportRequest& /*request*/) +{ + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +score::crypto::Expected IKeyFactory::DeriveKey( + const KeyDeriveRequest& /*request*/) +{ + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +score::crypto::Expected IKeyFactory::AgreeKey( + const KeyAgreeRequest& /*request*/) +{ + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +score::crypto::Expected IKeyFactory::WrapKey( + const WrapKeyRequest& /*request*/) +{ + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +score::crypto::Expected IKeyFactory::GetWrapKeySize( + const WrapKeyRequest& /*request*/) +{ + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +score::crypto::Expected IKeyFactory::UnwrapKey( + const UnwrapKeyRequest& /*request*/) +{ + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +score::crypto::Expected IKeyFactory::ExportKey( + const ProviderKeyHandle& /*handle*/, + score::mw::crypto::FormatType /*format*/) +{ + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +score::crypto::Expected IKeyFactory::GetExportKeySize( + const ProviderKeyHandle& /*handle*/, + score::mw::crypto::FormatType /*format*/) +{ + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +score::crypto::Expected IKeyFactory::GenerateKeyToSlot( + const KeySlotConfig& slot, + const KeyGenerationRequest& request, + IKeySlotHandler& slot_handler) +{ + auto handler_result = GenerateKey(request); + if (!handler_result.has_value()) + { + return score::crypto::make_unexpected(handler_result.error()); + } + return slot_handler.StoreKey(slot, *handler_result.value()); +} + +score::crypto::Expected +IKeyFactory::ImportKeyToSlot(const KeySlotConfig& slot, const KeyImportRequest& request, IKeySlotHandler& slot_handler) +{ + auto handler_result = ImportKey(request); + if (!handler_result.has_value()) + { + return score::crypto::make_unexpected(handler_result.error()); + } + return slot_handler.StoreKey(slot, *handler_result.value()); +} + +score::crypto::Expected +IKeyFactory::DeriveKeyToSlot(const KeySlotConfig& slot, const KeyDeriveRequest& request, IKeySlotHandler& slot_handler) +{ + auto handler_result = DeriveKey(request); + if (!handler_result.has_value()) + { + return score::crypto::make_unexpected(handler_result.error()); + } + return slot_handler.StoreKey(slot, *handler_result.value()); +} + +score::crypto::Expected +IKeyFactory::AgreeKeyToSlot(const KeySlotConfig& slot, const KeyAgreeRequest& request, IKeySlotHandler& slot_handler) +{ + auto handler_result = AgreeKey(request); + if (!handler_result.has_value()) + { + return score::crypto::make_unexpected(handler_result.error()); + } + return slot_handler.StoreKey(slot, *handler_result.value()); +} + +score::crypto::Expected +IKeyFactory::UnwrapKeyToSlot(const KeySlotConfig& slot, const UnwrapKeyRequest& request, IKeySlotHandler& slot_handler) +{ + auto handler_result = UnwrapKey(request); + if (!handler_result.has_value()) + { + return score::crypto::make_unexpected(handler_result.error()); + } + return slot_handler.StoreKey(slot, *handler_result.value()); +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/interfaces/i_key_factory.hpp b/score/crypto/daemon/key_management/interfaces/i_key_factory.hpp new file mode 100644 index 0000000..e6a4c13 --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/i_key_factory.hpp @@ -0,0 +1,156 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_I_KEY_FACTORY_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_I_KEY_FACTORY_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_handler.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_types.hpp" +#include "score/mw/crypto/api/common/types.hpp" + +#include +#include + +namespace score::crypto::daemon::key_management +{ + +class IKeySlotHandler; + +/// Provider-side factory for creating and importing key material. +/// +/// Each provider backend (OpenSSL, PKCS#11, TEE) implements this interface. +/// The factory is a long-lived singleton per provider: it is constructed once +/// during provider initialization and shared across all daemon operations. +/// +/// Every successful method returns an IKeyHandler::Sptr. The caller transfers +/// ownership to a KeyDataNode via KeyManagementService::RegisterKeyMaterial(). +/// +/// Methods other than GenerateKey have default implementations that return +/// kNotSupported, so providers implement only what they support. +class IKeyFactory +{ + public: + using Sptr = std::shared_ptr; + + IKeyFactory() = default; + + virtual ~IKeyFactory() = default; + + IKeyFactory(const IKeyFactory&) = delete; + IKeyFactory& operator=(const IKeyFactory&) = delete; + IKeyFactory(IKeyFactory&&) = delete; + IKeyFactory& operator=(IKeyFactory&&) = delete; + + /// Generate a new symmetric or asymmetric key. + /// + /// The provider determines key size from request.algorithm. + /// + /// Default: returns kNotSupported. + [[nodiscard]] virtual score::crypto::Expected + GenerateKey(const KeyGenerationRequest& request); + + /// Import raw key material. + /// + /// request.key_data is caller-owned and valid only for the duration of the + /// call. The implementation must copy the bytes immediately. + /// + /// Default: returns kNotSupported. + [[nodiscard]] virtual score::crypto::Expected + ImportKey(const KeyImportRequest& request); + + /// Derive a child key using the specified KDF. + /// + /// Default: returns kNotSupported. + [[nodiscard]] virtual score::crypto::Expected + DeriveKey(const KeyDeriveRequest& request); + + /// Perform key agreement and optionally apply a KDF in one atomic step. + /// + /// Default: returns kNotSupported. + [[nodiscard]] virtual score::crypto::Expected + AgreeKey(const KeyAgreeRequest& request); + + /// Wrap a key under a wrapping key and return the ciphertext blob. + /// + /// Both handles must belong to this provider. + /// Default: returns kNotSupported. + [[nodiscard]] virtual score::crypto::Expected + WrapKey(const WrapKeyRequest& request); + + /// Return the byte length the wrapped blob will occupy. + /// + /// Default: returns kNotSupported. + [[nodiscard]] virtual score::crypto::Expected + GetWrapKeySize(const WrapKeyRequest& request); + + /// Unwrap a wrapped key blob and return a live key handler. + /// + /// Default: returns kNotSupported. + [[nodiscard]] virtual score::crypto::Expected + UnwrapKey(const UnwrapKeyRequest& request); + + /// Export formatted key material (DER / PEM / RAW). + /// + /// Used for asymmetric keys where formatting is provider-specific. + /// For raw symmetric bytes, prefer IKeyHandler::Export(). + /// Default: returns kNotSupported. + [[nodiscard]] virtual score::crypto::Expected + ExportKey(const ProviderKeyHandle& handle, score::mw::crypto::FormatType format); + + /// Return the byte length of the exported key in the given format. + /// + /// Default: returns kNotSupported. + [[nodiscard]] virtual score::crypto::Expected + GetExportKeySize(const ProviderKeyHandle& handle, score::mw::crypto::FormatType format); + + /// Generate a key atomically into a persistent slot. + /// + /// The default implementation composes GenerateKey() + slot_handler.StoreKey(). + /// Providers that can generate non-exportable keys directly on hardware + /// (e.g., PKCS#11 C_GenerateKey with CKA_TOKEN=TRUE) should override this + /// to keep the key material inside the secure boundary. + /// + /// Default: GenerateKey(request) + slot_handler.StoreKey(slot, *handler). + [[nodiscard]] virtual score::crypto::Expected + GenerateKeyToSlot(const KeySlotConfig& slot, const KeyGenerationRequest& request, IKeySlotHandler& slot_handler); + + /// Import raw key material directly into a persistent slot. + /// + /// Default: ImportKey(request) + slot_handler.StoreKey(slot, *handler). + [[nodiscard]] virtual score::crypto::Expected + ImportKeyToSlot(const KeySlotConfig& slot, const KeyImportRequest& request, IKeySlotHandler& slot_handler); + + /// Derive a key and store it directly into a persistent slot. + /// + /// Default: DeriveKey(request) + slot_handler.StoreKey(slot, *handler). + [[nodiscard]] virtual score::crypto::Expected + DeriveKeyToSlot(const KeySlotConfig& slot, const KeyDeriveRequest& request, IKeySlotHandler& slot_handler); + + /// Perform key agreement and store the resulting key directly into a persistent slot. + /// + /// Default: AgreeKey(request) + slot_handler.StoreKey(slot, *handler). + [[nodiscard]] virtual score::crypto::Expected + AgreeKeyToSlot(const KeySlotConfig& slot, const KeyAgreeRequest& request, IKeySlotHandler& slot_handler); + + /// Unwrap a key and store it directly into a persistent slot. + /// + /// Default: UnwrapKey(request) + slot_handler.StoreKey(slot, *handler). + [[nodiscard]] virtual score::crypto::Expected + UnwrapKeyToSlot(const KeySlotConfig& slot, const UnwrapKeyRequest& request, IKeySlotHandler& slot_handler); +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_I_KEY_FACTORY_HPP diff --git a/score/crypto/daemon/key_management/interfaces/i_key_handler.hpp b/score/crypto/daemon/key_management/interfaces/i_key_handler.hpp new file mode 100644 index 0000000..da4a8da --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/i_key_handler.hpp @@ -0,0 +1,83 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_I_KEY_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_I_KEY_HANDLER_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_types.hpp" + +#include +#include + +namespace score::crypto::daemon::key_management +{ + +/// Per-key object that owns the lifetime of a single key material entry. +/// +/// An IKeyHandler is created by IKeyFactory (for generated/imported keys) or +/// IKeySlotHandler (for slot-loaded keys). Its ownership is transferred to a +/// KeyDataNode in the per-provider KeyRegistry. +/// +/// Thread safety: individual instances are not thread-safe. The DataNode +/// exclusiveAccess flag ensures mutual exclusion for the KeyDataNode path. +/// +/// Lifecycle contract: +/// - Release() must be called exactly once before destruction. +/// - The destructor always calls Release() as a fallback, so callers that +/// simply destroy the IKeyHandler without calling Release() are safe. +/// - After Release() the object is spent; calling Release() again returns +/// true without repeating the zeroization (idempotent). + +class IKeyHandler +{ + public: + using Sptr = std::shared_ptr; + + IKeyHandler() = default; + + virtual ~IKeyHandler() = default; + + IKeyHandler(const IKeyHandler&) = delete; + IKeyHandler& operator=(const IKeyHandler&) = delete; + IKeyHandler(IKeyHandler&&) = delete; + IKeyHandler& operator=(IKeyHandler&&) = delete; + + /// Read provider metadata for this key (algorithm, size, exportability). + /// + /// The returned reference is valid for the lifetime of the IKeyHandler. + [[nodiscard]] virtual const ProviderKeyHandle& GetHandle() const noexcept = 0; + + /// Returns the numeric ProviderId (uint16_t) that owns this key handler. + /// + /// Used by crypto operation handlers to verify the key comes from the + /// expected provider before performing provider-specific operations. + [[nodiscard]] virtual common::ProviderId GetProviderId() const noexcept = 0; + + /// Securely zeroize and release provider-held key material. + /// + /// After a successful call, the key material is gone. Idempotent: a second + /// call returns true without attempting another zeroization. + [[nodiscard]] virtual score::crypto::Expected + Release() = 0; + + /// Export raw key material into a SecureKeyBytes buffer. + /// + /// Returns kNotPermitted for non-exportable keys or after Release() was called. + [[nodiscard]] virtual score::crypto::Expected + Export() const = 0; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_I_KEY_HANDLER_HPP diff --git a/score/crypto/daemon/key_management/interfaces/i_key_slot_catalog.hpp b/score/crypto/daemon/key_management/interfaces/i_key_slot_catalog.hpp new file mode 100644 index 0000000..4b52b1f --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/i_key_slot_catalog.hpp @@ -0,0 +1,54 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_I_KEY_SLOT_CATALOG_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_I_KEY_SLOT_CATALOG_HPP + +namespace score::crypto::daemon::key_management +{ + +class SlotRegistry; + +/// @brief Abstract source of key slot definitions. +/// +/// A catalog is a **one-shot loader**: it is instantiated, `Load()` is called +/// exactly once to populate the registry, and the catalog object may then be +/// discarded. This keeps SlotRegistry a pure registry with no knowledge +/// of where slot definitions come from. +/// +/// Current implementations: +/// - `ConfigDrivenSlotCatalog` — reads slot definitions from the parsed KeyConfig +/// +/// Future implementations: +/// - `SecureStoreCatalog` — reads provisioned slot metadata from a TEE-backed store +/// - `Pkcs11SlotCatalog` — enumerates PKCS#11 token objects and registers them +/// +/// @note Catalog implementations MUST be idempotent: calling `Load()` on an +/// already-populated registry is safe and duplicate slot names are +/// rejected by the registry boundary instead of overwriting existing slots. +class IKeySlotCatalog +{ + public: + virtual ~IKeySlotCatalog() = default; + + /// @brief Register all slots from this catalog into the given registry. + /// + /// Each slot is registered via `SlotRegistry::RegisterSlot(KeySlotConfig)`. + /// The catalog does NOT retain a reference to the registry after this call. + /// + /// @param registry The central SlotRegistry to populate. + virtual void Load(SlotRegistry& registry) = 0; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_I_KEY_SLOT_CATALOG_HPP diff --git a/score/crypto/daemon/key_management/interfaces/i_key_slot_handler.cpp b/score/crypto/daemon/key_management/interfaces/i_key_slot_handler.cpp new file mode 100644 index 0000000..dc00cda --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/i_key_slot_handler.cpp @@ -0,0 +1,33 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp" + +#include "score/crypto/daemon/common/daemon_error.hpp" + +namespace score::crypto::daemon::key_management +{ + +score::crypto::Expected IKeySlotHandler::StoreKey( + const KeySlotConfig& /*slot*/, + IKeyHandler& /*handler*/) +{ + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +score::crypto::Expected IKeySlotHandler::ClearSlot( + const KeySlotConfig& /*slot*/) +{ + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp b/score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp new file mode 100644 index 0000000..c402003 --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp @@ -0,0 +1,91 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_I_KEY_SLOT_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_I_KEY_SLOT_HANDLER_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_handler.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_types.hpp" +#include "score/mw/crypto/api/common/types.hpp" + +#include + +namespace score::crypto::daemon::key_management +{ + +/// Interface for provider-specific key slot operations. +/// +/// A slot handler manages the storage backend for one provider category: +/// - FileBackedSlotHandler: file system (pure-SW provider, daemon manages I/O) +/// - Pkcs11KeySlotHandler: PKCS#11 token (HW/SW HSM manages storage) +/// - TeeKeySlotHandler: TEE/PSA (secure enclave manages storage) +/// +/// LoadKey is the primary operation: it retrieves key material from the slot +/// and returns an IKeyHandler that owns the material for its lifetime. +/// +/// The key management module depends only on this interface. +class IKeySlotHandler +{ + public: + using Sptr = std::shared_ptr; + + IKeySlotHandler() = default; + + virtual ~IKeySlotHandler() = default; + + IKeySlotHandler(const IKeySlotHandler&) = delete; + IKeySlotHandler& operator=(const IKeySlotHandler&) = delete; + IKeySlotHandler(IKeySlotHandler&&) = delete; + IKeySlotHandler& operator=(IKeySlotHandler&&) = delete; + + /// Load key material from the slot and return a key handler that owns it. + /// + /// For file-backed slots: reads file bytes, delegates to provider ImportKey. + /// For PKCS#11 slots: opens a session, finds the token object, wraps it. + /// For TEE slots: calls psa_open_key() or equivalent. + /// + /// The returned IKeyHandler must be transferred to a KeyDataNode + /// (via RegisterKeyMaterial) immediately; the caller must not hold bare references. + [[nodiscard]] virtual score::crypto::Expected + LoadKey(const KeySlotConfig& slot) = 0; + + /// Query slot occupancy state (kEmpty or kOccupied). + [[nodiscard]] virtual score::crypto::Expected + GetSlotState(const KeySlotConfig& slot) = 0; + + /// Get slot metadata (state, algorithm, provider, permissions). + [[nodiscard]] virtual score::crypto::Expected + GetSlotInfo(const KeySlotConfig& slot) = 0; + + /// Store key material from an IKeyHandler into a persistent slot. + /// + /// Used by GenerateKeyToSlot (default compose path) and PersistKey. + /// Default: returns kNotSupported. + [[nodiscard]] virtual score::crypto::Expected + StoreKey(const KeySlotConfig& slot, IKeyHandler& handler); + + /// Erase key material from a persistent slot. + /// + /// After this call GetSlotState() must return kEmpty. + /// Default: returns kNotSupported. + [[nodiscard]] virtual score::crypto::Expected + ClearSlot(const KeySlotConfig& slot); +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_I_KEY_SLOT_HANDLER_HPP diff --git a/score/crypto/daemon/key_management/interfaces/key_management_operations.hpp b/score/crypto/daemon/key_management/interfaces/key_management_operations.hpp new file mode 100644 index 0000000..2f7c219 --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/key_management_operations.hpp @@ -0,0 +1,82 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_MANAGEMENT_OPERATIONS_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_MANAGEMENT_OPERATIONS_HPP + +#include "score/crypto/daemon/common/types.hpp" + +namespace score::crypto::daemon::key_management::operations +{ + +using OperationAction = common::OperationAction; + +/// @brief Operation codes for key management requests forwarded to providers. +/// +/// Codes are spaced to allow future insertions without renumbering +/// existing operations. +/// +/// Note: CTX_CREATE and RESOLVE_RESOURCE are mediator-level operations +/// defined in mediator/mediator_operations.hpp, not provider operations. + +// KEY_GENERATE (ephemeral) +// Request: data_node_id = context_id, +// param[0]: string — key algorithm (e.g., "AES-256", "HMAC-SHA256") +// param[1]: uint32 — permissions bitmask (KeyOperationPermission) +// Response: status_code (SUCCESS/error) +// param[0]: uint64 — daemon-assigned ephemeral key resource id +// param[1]: uint16 — primary provider id (optional) +// Effect: Generates ephemeral key material, stores in daemon key pool, ref_count = 1 +inline constexpr OperationAction KEY_GENERATE = 0x10; + +inline constexpr OperationAction KEY_GENERATE_TO_SLOT = 0x11; +inline constexpr OperationAction KEY_PERSIST = 0x12; +inline constexpr OperationAction KEY_DERIVE = 0x20; +inline constexpr OperationAction KEY_DERIVE_TO_SLOT = 0x21; +inline constexpr OperationAction KEY_AGREE = 0x30; +inline constexpr OperationAction KEY_AGREE_TO_SLOT = 0x31; +inline constexpr OperationAction KEY_WRAP = 0x40; +inline constexpr OperationAction KEY_GET_WRAP_SIZE = 0x41; +inline constexpr OperationAction KEY_UNWRAP = 0x50; +inline constexpr OperationAction KEY_UNWRAP_TO_SLOT = 0x51; +inline constexpr OperationAction KEY_IMPORT = 0x60; +inline constexpr OperationAction KEY_IMPORT_TO_SLOT = 0x61; + +// KEY_LOAD +// Request: data_node_id = context_id, +// param[0]: uint64 — slot resource id (from ResolveResource with kKeySlot) +// Response: status_code (SUCCESS/error) +// param[0]: uint64 — daemon-assigned ephemeral key resource id (live key material) +// param[1]: uint16 — primary provider id (optional) +// Effect: Loads key material from the specified key slot into an ephemeral key, +// stores in daemon key pool with ref_count = 1. +// The slot must contain key material (state != kEmpty). +inline constexpr OperationAction KEY_LOAD = 0x70; + +inline constexpr OperationAction KEY_EXPORT = 0x80; +inline constexpr OperationAction KEY_GET_EXPORT_SIZE = 0x81; +inline constexpr OperationAction KEY_SLOT_INFO = 0xB0; +inline constexpr OperationAction KEY_CLEAR = 0xE0; + +// KEY_RELEASE +// Request: data_node_id = context_id, +// param[0]: uint64 — resource id to release +// Response: status_code (SUCCESS/error) +// no output parameters +// Effect: Decrements ref-count; frees key material when ref_count reaches 0 +inline constexpr OperationAction KEY_RELEASE = 0xF0; + +inline constexpr OperationAction KEY_MGMT_CUSTOM_OP_START = 1 << (std::numeric_limits::digits - 1); + +} // namespace score::crypto::daemon::key_management::operations + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_MANAGEMENT_OPERATIONS_HPP diff --git a/score/crypto/daemon/key_management/interfaces/key_slot_config.hpp b/score/crypto/daemon/key_management/interfaces/key_slot_config.hpp new file mode 100644 index 0000000..1653fed --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/key_slot_config.hpp @@ -0,0 +1,219 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_SLOT_CONFIG_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_SLOT_CONFIG_HPP + +#include "score/crypto/daemon/common/types.hpp" +#include "score/mw/crypto/api/common/types.hpp" + +#include +#include +#include +#include + +namespace score::crypto::daemon::key_management +{ + +/// @brief Access control policy for a key slot. +struct AccessPolicy +{ + /// @brief UIDs permitted to read from this slot (LoadKey, GetKeySlotInfo, ResolveResource). + std::vector allowed_uids; + + /// @brief UIDs permitted to write to this slot (GenerateKey, persist/import). + /// + /// A UID not in this list can still load and use existing keys from the slot; + /// it simply cannot create or replace key material. + std::vector allowed_write_uids; +}; + +/// @brief Well-known keys for SlotDeploymentInfo::metadata. +/// +/// Metadata captures slot-level lifecycle state that the slot management +/// layer (SlotRegistry, catalogs) reads without involving a provider. +/// All values are UTF-8 strings; consumers must validate before use. +namespace metadata_keys +{ +/// Slot availability override: "active" | "disabled" | "unavailable". +/// +/// When absent the slot is assumed active. The catalog or deployment +/// loader writes this key; the SlotRegistry reads it at load time. +inline constexpr std::string_view kAvailability = "availability"; + +/// ISO-8601 UTC timestamp of the last successful key provisioning. +/// Example: "2025-11-03T08:42:00Z" +inline constexpr std::string_view kProvisionedAt = "provisioned_at"; + +/// Monotonically increasing update counter (decimal string). +/// Incremented by the writer on every key replacement. +inline constexpr std::string_view kUpdateCounter = "update_counter"; + +/// Hash of the key material (hex-encoded digest). +/// Example: "sha256:a1b2c3d4e5f6..." +inline constexpr std::string_view kHash = "hash"; + +/// Slot name containing the Key Encryption Key (KEK) for encrypting/decrypting this key. +/// Example: "vehicle/master-key" +inline constexpr std::string_view kKekKeySlotName = "kek.keyslotname"; + +/// Algorithm used by the Key Encryption Key (KEK). +/// Example: "AES-256-GCM", "AES-256-CBC" +inline constexpr std::string_view kKekAlgorithm = "kek.algo"; + +/// Initialization Vector (IV) for the Key Encryption Key (KEK) operations (hex-encoded). +/// Example: "0102030405060708090a0b0c0d0e0f10" +inline constexpr std::string_view kKekIv = "kek.iv"; +} // namespace metadata_keys + +/// @brief Well-known keys for SlotDeploymentInfo::key_properties. +/// +/// Each IKeySlotHandler reads only the entries it understands, silently +/// ignoring others. Adding a new backend requires only a daemon config +/// change — no client API change and no application rebuild. +namespace deployment_keys +{ +/// File path for file-backed SW providers (e.g., OpenSSL + FileBackedSlotHandler). +inline constexpr std::string_view kKeyPath = "key_path"; +/// Encoding of the key data at kKeyPath: "raw", "pem", "der". +inline constexpr std::string_view kKeyFormat = "key_format"; +/// Plain text key material (hex-encoded or base64-encoded). +/// **Warning**: Only use for testing/development. Avoid in production. +/// Example: "0102030405060708090a0b0c0d0e0f10" +inline constexpr std::string_view kKey = "key"; +/// PKCS#11 CKA_LABEL — human-readable object label inside the token. +inline constexpr std::string_view kPkcs11Label = "pkcs11.label"; +/// PKCS#11 CKA_ID — hex-encoded binary object ID (e.g., "0102abcd"). +inline constexpr std::string_view kPkcs11ObjectId = "pkcs11.object_id"; +/// PKCS#11 CKA_CLASS as string: "secret_key", "private_key", "public_key". +inline constexpr std::string_view kPkcs11ObjectClass = "pkcs11.object_class"; +/// TEE / PSA persistent key identifier (decimal or hex string). +inline constexpr std::string_view kTeeKeyId = "tee.key_id"; +/// PSA Crypto key identifier (uint32 expressed as decimal string). +inline constexpr std::string_view kPsaKeyId = "psa.key_id"; +} // namespace deployment_keys + +/// @brief Dynamic information loaded from a slot's deployment descriptor. +/// +/// The deployment path (file or folder) is read by the DeploymentLoader at +/// slot load time. The content is provider-agnostic at the struct level; +/// each IKeySlotHandler interprets the `key_properties` entries it understands. +/// +/// For file-backed (OpenSSL) providers: +/// key_properties = {"key_path": "/path/to/key.bin", "key_format": "raw"} +/// +/// For PKCS#11 / HSM providers: +/// key_properties = {"pkcs11.label": "my_hmac", "pkcs11.object_id": "0102ab"} +/// +/// For TEE / PSA providers: +/// key_properties = {"tee.key_id": "42"} +struct SlotDeploymentInfo +{ + /// @brief Slot-level dynamic metadata (extensible string map). + /// + /// Well-known keys: metadata_keys::kAvailability, metadata_keys::kLabel, + /// metadata_keys::kProvisionedAt, metadata_keys::kUpdateCounter. + /// Providers and extensions may define additional entries. + std::unordered_map metadata; + + /// @brief Provider-specific key identification and location properties. + /// + /// Interpretation is provider-dependent: file-backed handlers read + /// key_path/key_format; PKCS#11 handlers read pkcs11.label/object_id; + /// TEE handlers read tee.key_id. + std::unordered_map key_properties; +}; + +/// @brief Immutable configuration for a key slot. +/// +/// Owned centrally by the SlotRegistry. KeySlotDataNodes hold only a +/// SlotHandle referencing back to the central registry — they do NOT +/// copy this struct. +/// +/// All fields are immutable after registration. Dynamic slot and key +/// information (availability, provider-specific identifiers, key material +/// location) is loaded from the deployment descriptor at `deployment_path`. +/// +/// ### Provider Identity: Names vs. IDs +/// +/// Configuration time: `provider_names` holds human-readable strings from config. +/// Runtime: `provider_ids` holds numeric IDs assigned by ProviderManager. +/// The conversion happens in SlotRegistry::ResolveProviderIds(). +/// +/// ### Multi-provider model +/// `provider_ids` is an ordered list of providers permitted to work with this +/// key slot. The first entry (`provider_ids[0]`) is the **primary provider** — +/// the only provider allowed to *modify* key material (generate, write, delete). +/// Subsequent entries are **secondary providers** that may *consume* the key for +/// crypto operations (MAC, cipher, sign, etc.) but cannot mutate it. +struct KeySlotConfig +{ + std::string slot_name; ///< Human-readable resource ID (e.g., "vehicle/hmac-256") + std::string algorithm; ///< Algorithm string (e.g., "HMAC-SHA256", "AES-256-CMAC") + + /// @brief Config-time: Ordered provider names from config. [0] is the primary. + std::vector provider_names; + + /// @brief Runtime: Ordered numeric provider IDs. [0] is the primary (sole writer). + /// Populated by SlotRegistry::ResolveProviderIds() after ProviderManager + /// initialization. Subsequent entries are read-only consumers. + std::vector provider_ids; + + /// @brief Permitted crypto operations for keys loaded from this slot. + score::mw::crypto::KeyOperationPermission allowed_operations{score::mw::crypto::KeyOperationPermission::kNone}; + + /// @brief UID-based access control for this slot. + AccessPolicy access_policy; + + /// @brief Path to the deployment descriptor (file or folder). + /// + /// The deployment descriptor holds dynamic slot metadata and provider-specific + /// key identification/location data. Read by DeploymentLoader at slot load time. + /// Must be an absolute path with no ".." traversal components. + std::string deployment_path; + + /// @brief Format of the deployment descriptor: "kv" (default), "json", "bin". + std::string deployment_format{"kv"}; + + // ------------------------------------------------------------------- + // Convenience accessors + // ------------------------------------------------------------------- + + /// @brief Return the primary provider ID (numeric, sole writer). + common::ProviderId GetPrimaryProviderId() const noexcept + { + return provider_ids.empty() ? common::kInvalidProviderId : provider_ids.front(); + } + + /// @brief True when numeric provider `id` is listed in provider_ids (primary or secondary). + bool IsProviderAllowed(common::ProviderId id) const noexcept + { + for (const auto& p : provider_ids) + { + if (p == id) + { + return true; + } + } + return false; + } + + /// @brief True when the KeySlotConfig is structurally valid (at least one provider). + bool IsValid() const noexcept + { + return !slot_name.empty() && !provider_ids.empty(); + } +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_SLOT_CONFIG_HPP diff --git a/score/crypto/daemon/key_management/interfaces/key_types.hpp b/score/crypto/daemon/key_management/interfaces/key_types.hpp new file mode 100644 index 0000000..8f68dc8 --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/key_types.hpp @@ -0,0 +1,183 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_TYPES_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_TYPES_HPP + +#include "score/crypto/daemon/common/types.hpp" +#include "score/mw/crypto/api/common/types.hpp" +#include "score/mw/crypto/api/config/key_operation_params.hpp" + +#include +#include +#include +#include + +namespace score::crypto::daemon::key_management +{ + +// --------------------------------------------------------------------------- +// ProviderKeyHandle — opaque per-key runtime reference +// --------------------------------------------------------------------------- + +/// Runtime reference to key material held by a specific provider. +/// +/// The opaque_id is allocated, interpreted, and freed exclusively by the +/// provider that issued the handle. Callers must not dereference or +/// arithmetic-shift opaque_id. +/// +/// OpenSSL : opaque_id maps to raw key bytes managed by the factory +/// PKCS#11 : opaque_id maps to (CK_SESSION_HANDLE, CK_OBJECT_HANDLE) +/// TEE/PSA : opaque_id = persistent key identifier from the TEE driver +struct ProviderKeyHandle +{ + std::uint64_t opaque_id{0U}; + common::ProviderId provider_id{common::kInvalidProviderId}; + bool is_asymmetric{false}; + score::mw::crypto::KeyOperationPermission permissions{score::mw::crypto::KeyOperationPermission::kNone}; + common::AlgorithmId algorithm{}; + std::size_t key_size{0U}; +}; + +// --------------------------------------------------------------------------- +// Request parameter structs +// --------------------------------------------------------------------------- + +/// Parameters for ephemeral symmetric or asymmetric key generation. +struct KeyGenerationRequest +{ + common::AlgorithmId algorithm{}; + score::mw::crypto::KeyOperationPermission permissions{score::mw::crypto::KeyOperationPermission::kAll}; + /// @brief Operations the public key is permitted to perform (asymmetric only). + /// Use kExport bit to control public key exportability. + std::optional public_key_permissions{std::nullopt}; + score::mw::crypto::ExtendedParameters provider_properties{}; +}; + +/// Parameters for raw key material import. +/// +/// key_data points to caller-owned memory that remains valid for the +/// duration of the call only. The callee must copy the bytes. +struct KeyImportRequest +{ + const std::uint8_t* key_data{nullptr}; + std::size_t key_data_size{0U}; + common::AlgorithmId algorithm{}; + score::mw::crypto::FormatType format{score::mw::crypto::FormatType::kDer}; + score::mw::crypto::KeyOperationPermission permissions{score::mw::crypto::KeyOperationPermission::kAll}; + score::mw::crypto::ExtendedParameters provider_properties{}; +}; + +/// Parameters for key derivation (HKDF, PBKDF2, TLS 1.3 KDF, etc.). +/// +/// Uses the same KdfParameters as the client API to avoid a double +/// flattened/structured translation layer inside the daemon. +struct KeyDeriveRequest +{ + ProviderKeyHandle base_key{}; + common::AlgorithmId output_algorithm{}; + score::mw::crypto::KdfParameters kdf{}; + score::mw::crypto::KeyOperationPermission permissions{score::mw::crypto::KeyOperationPermission::kAll}; +}; + +/// Parameters for key agreement (ECDH, X25519, ML-KEM). +/// +/// When kdf is set, the provider performs agree-then-derive atomically, +/// matching AgreeKeyParams semantics from the client API. +struct KeyAgreeRequest +{ + ProviderKeyHandle private_key{}; + const std::uint8_t* peer_public_key{nullptr}; + std::size_t peer_public_key_size{0U}; + /// Agreement mechanism (e.g., "ECDH", "X25519"). + common::AlgorithmId agreement_algorithm{}; + /// Algorithm of the agreed or derived output key. + common::AlgorithmId output_algorithm{}; + /// @brief Format of the peer public key data. Defaults to raw/uncompressed. + std::optional public_key_format{std::nullopt}; + score::mw::crypto::KeyOperationPermission permissions{score::mw::crypto::KeyOperationPermission::kAll}; + /// Optional KDF for combined agree-then-derive (e.g., ECIES, TLS key exchange). + std::optional kdf{std::nullopt}; +}; + +// --------------------------------------------------------------------------- +// WrapKeyRequest / UnwrapKeyRequest — provider-level wrap/unwrap parameters +// --------------------------------------------------------------------------- + +/// Parameters for wrapping one key under another (provider level). +/// +/// Both handles are ProviderKeyHandles already held by the same provider. +/// Cross-provider wrap is not supported; both keys must be in the same provider. +struct WrapKeyRequest +{ + ProviderKeyHandle key_to_wrap{}; + ProviderKeyHandle wrapping_key{}; + std::optional wrapping_algorithm{std::nullopt}; + const std::uint8_t* iv{nullptr}; + std::size_t iv_size{0U}; + const std::uint8_t* aad{nullptr}; + std::size_t aad_size{0U}; +}; + +/// Parameters for unwrapping a wrapped key blob (provider level). +/// +/// key_data / key_data_size point to caller-owned memory valid for the call. +struct UnwrapKeyRequest +{ + const std::uint8_t* wrapped_data{nullptr}; + std::size_t wrapped_data_size{0U}; + ProviderKeyHandle wrapping_key{}; + common::AlgorithmId inner_key_algorithm{}; + std::optional wrapping_algorithm{std::nullopt}; + const std::uint8_t* iv{nullptr}; + std::size_t iv_size{0U}; + const std::uint8_t* aad{nullptr}; + std::size_t aad_size{0U}; + score::mw::crypto::KeyOperationPermission permissions{score::mw::crypto::KeyOperationPermission::kAll}; +}; + +// --------------------------------------------------------------------------- +// SecureKeyBytes — RAII container for exported key material +// --------------------------------------------------------------------------- + +/// Bytes are securely zeroized on destruction. +/// +/// Ephemeral keys remain associated with their creating provider for the +/// duration of the context. Must not be stored in persistent data structures. +struct SecureKeyBytes +{ + std::vector bytes; + + SecureKeyBytes() = default; + explicit SecureKeyBytes(std::size_t size) : bytes(size) {} + + ~SecureKeyBytes() + { + for (auto& b : bytes) + { + b = 0U; + } + } + + SecureKeyBytes(const SecureKeyBytes&) = delete; + SecureKeyBytes& operator=(const SecureKeyBytes&) = delete; + SecureKeyBytes(SecureKeyBytes&&) noexcept = default; + SecureKeyBytes& operator=(SecureKeyBytes&&) noexcept = default; +}; + +// --------------------------------------------------------------------------- +// Well-known operation constants +// --------------------------------------------------------------------------- + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_TYPES_HPP diff --git a/score/crypto/daemon/key_management/key_management_module.cpp b/score/crypto/daemon/key_management/key_management_module.cpp new file mode 100644 index 0000000..766aacf --- /dev/null +++ b/score/crypto/daemon/key_management/key_management_module.cpp @@ -0,0 +1,78 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/key_management_module.hpp" + +#include "score/crypto/daemon/key_management/slot/config_driven_slot_catalog.hpp" +#include "score/crypto/daemon/provider/i_provider.hpp" + +#include +#include + +namespace score::crypto::daemon::key_management +{ + +namespace +{ + +} // namespace + +KeyManagementModule::Sptr KeyManagementModule::Create(data_manager::IDataManager::Sptr data_manager, + provider::ProviderManager::Sptr provider_manager, + const config::KeyConfig& key_config) +{ + auto module = Sptr(new KeyManagementModule()); + + module->m_provider_manager = provider_manager; + module->m_slot_registry = std::make_shared(); + + // Load slot definitions from parsed configuration. + ConfigDrivenSlotCatalog catalog{key_config}; + catalog.Load(*module->m_slot_registry); + + // Create the core key management service with all dependencies. + module->m_service = + std::make_shared(std::move(data_manager), provider_manager, module->m_slot_registry); + + // Inject the service into every registered provider so that factory-created + // handlers can use it without mediator-level injection. + provider_manager->ForEachProvider([&](const auto& /*id*/, const auto& provider) { + provider->SetKeyManagementService(module->m_service); + }); + + // Resolve provider names → numeric IDs in all slots. + // This must happen after providers are registered and SetKeyManagementService is called. + module->m_slot_registry->ResolveProviderIds(*provider_manager); + + std::cout << LOG_PREFIX + << "Key management module initialized from config. Slots: " << module->m_slot_registry->GetSlotCount() + << '\n'; + + return module; +} + +SlotRegistry::Sptr KeyManagementModule::GetSlotRegistry() const +{ + return m_slot_registry; +} + +KeyManagementService::Sptr KeyManagementModule::GetService() const +{ + return m_service; +} + +provider::ProviderManager::Sptr KeyManagementModule::GetProviderManager() const +{ + return m_provider_manager; +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/key_management_module.hpp b/score/crypto/daemon/key_management/key_management_module.hpp new file mode 100644 index 0000000..11480e8 --- /dev/null +++ b/score/crypto/daemon/key_management/key_management_module.hpp @@ -0,0 +1,88 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_MANAGEMENT_MODULE_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_MANAGEMENT_MODULE_HPP + +#include "score/crypto/daemon/config/inc/config.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" +#include "score/crypto/daemon/key_management/core/key_management_service.hpp" +#include "score/crypto/daemon/key_management/slot/slot_registry.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" + +#include +#include + +namespace score::crypto::daemon::key_management +{ + +/// @brief Composition root for the key management subsystem. +/// +/// Wires together the slot registry and provider manager in dependency order: +/// 1. Instantiates a slot catalog (ConfigDrivenSlotCatalog) and calls `Load(*slot_registry)` +/// to populate `SlotRegistry` with slot definitions. +/// 2. Exposes `GetSlotRegistry()` and `GetProviderManager()` for the mediator +/// to create `KeyManagementContextDataNode` instances per client session. +/// +/// The executor pattern is replaced by per-session context DataNodes created in +/// the mediator on `CTX_CREATE` operations. This module is created once at +/// daemon startup and its pointers are shared across all sessions. +class KeyManagementModule +{ + public: + using Sptr = std::shared_ptr; + + /// @brief Initialize with parsed configuration (production path). + /// + /// Uses ConfigDrivenSlotCatalog to load slot definitions from the daemon's + /// parsed KeyConfig section. Supports OpenSSL file-backed, PKCS#11, TEE, + /// and any future provider — slot entries carry a deployment_path that each + /// handler loads at runtime. + /// + /// @param data_manager Thread-safe data store for session nodes. + /// @param provider_manager Provider registry for per-session handler dispatch. + /// @param key_config Parsed key configuration section. + /// @return A fully initialized KeyManagementModule, or nullptr on failure. + static Sptr Create(data_manager::IDataManager::Sptr data_manager, + provider::ProviderManager::Sptr provider_manager, + const config::KeyConfig& key_config); + + ~KeyManagementModule() = default; + + // Non-copyable, non-movable (shared via Sptr) + KeyManagementModule(const KeyManagementModule&) = delete; + KeyManagementModule& operator=(const KeyManagementModule&) = delete; + KeyManagementModule(KeyManagementModule&&) = delete; + KeyManagementModule& operator=(KeyManagementModule&&) = delete; + + /// @brief Get the key config manager (central slot registry). + SlotRegistry::Sptr GetSlotRegistry() const; + + /// @brief Get the core key management service. + KeyManagementService::Sptr GetService() const; + + /// @brief Get the provider manager (for context node creation in mediator). + provider::ProviderManager::Sptr GetProviderManager() const; + + private: + KeyManagementModule() = default; + + SlotRegistry::Sptr m_slot_registry; + KeyManagementService::Sptr m_service; + provider::ProviderManager::Sptr m_provider_manager; + + static constexpr std::string_view LOG_PREFIX = "[KEY_MGMT_MODULE] "; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_MANAGEMENT_MODULE_HPP diff --git a/score/crypto/daemon/key_management/nodes/key_data_node.hpp b/score/crypto/daemon/key_management/nodes/key_data_node.hpp new file mode 100644 index 0000000..489b0ee --- /dev/null +++ b/score/crypto/daemon/key_management/nodes/key_data_node.hpp @@ -0,0 +1,110 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_DATA_NODE_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_DATA_NODE_HPP + +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/key_management/core/key_entry.hpp" +#include "score/crypto/daemon/key_management/core/key_registry.hpp" + +#include +#include +#include + +namespace score::crypto::daemon::key_management +{ + +/// DataNode placed in the client's tree that references a shared +/// KeyEntry living in the per-provider KeyRegistry. +/// +/// On construction, increments the KeyEntry's reference count. +/// On destruction, decrements it. When the count reaches zero, the +/// provided cleanup callback unregisters the key from the registry and +/// triggers secure zeroization of key material. +/// +/// This node is the daemon-side equivalent of a client's CryptoResourceGuard: +/// each guard ↔ one KeyDataNode ↔ one AddRef/Release pair on the shared +/// KeyEntry. +/// +/// exclusiveAccess = false: multiple threads may read concurrently. +class KeyDataNode final : public data_manager::DataNode +{ + public: + /// Callback invoked when the reference count reaches zero. + /// The registry uses this to unregister the key. + using UnregisterCallback = std::function; + + /// @param key_entry Shared KeyEntry from the registry. + /// @param registry_id ID assigned by KeyRegistry. + /// @param client_id Client that owns this reference. + /// @param on_last_release Called when Release() drops ref_count to zero. + [[nodiscard]] data_manager::DataNodeType GetNodeType() const noexcept override + { + return data_manager::DataNodeType::kKeyData; + } + + KeyDataNode(std::shared_ptr key_entry, + KeyRegistryId registry_id, + data_manager::ClientId client_id, + UnregisterCallback on_last_release) + : DataNode(false), + m_key_entry{std::move(key_entry)}, + m_registry_id{registry_id}, + m_client_id{client_id}, + m_on_last_release{std::move(on_last_release)} + { + m_key_entry->AddRef(m_client_id); + } + + /// Decrements the shared KeyEntry's reference count. + /// If the count reaches zero, invokes the unregister callback so the + /// registry can remove the key and allow its destructor to run. + ~KeyDataNode() override + { + if (m_key_entry != nullptr) + { + const bool last = m_key_entry->Release(m_client_id); + if (last && m_on_last_release) + { + m_on_last_release(m_registry_id); + } + } + } + + KeyDataNode(const KeyDataNode&) = delete; + KeyDataNode& operator=(const KeyDataNode&) = delete; + KeyDataNode(KeyDataNode&&) = delete; + KeyDataNode& operator=(KeyDataNode&&) = delete; + + /// Access the underlying KeyEntry (e.g., for key resolution in BindKeyToContext). + [[nodiscard]] std::shared_ptr GetKeyEntry() const noexcept + { + return m_key_entry; + } + + /// Registry-assigned identifier for the shared key. + [[nodiscard]] KeyRegistryId GetRegistryId() const noexcept + { + return m_registry_id; + } + + private: + std::shared_ptr m_key_entry; + KeyRegistryId m_registry_id; + data_manager::ClientId m_client_id; + UnregisterCallback m_on_last_release; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_DATA_NODE_HPP diff --git a/score/crypto/daemon/key_management/nodes/key_slot_data_node.hpp b/score/crypto/daemon/key_management/nodes/key_slot_data_node.hpp new file mode 100644 index 0000000..fc65e39 --- /dev/null +++ b/score/crypto/daemon/key_management/nodes/key_slot_data_node.hpp @@ -0,0 +1,100 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_SLOT_DATA_NODE_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_SLOT_DATA_NODE_HPP + +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/key_management/slot/slot_registry.hpp" +#include "score/mw/crypto/api/common/types.hpp" + +#include + +namespace score::crypto::daemon::key_management +{ + +/// @brief DataNode for a resolved persistent key slot. +/// +/// Created on ResolveResource(), lives for the connection lifetime. +/// exclusiveAccess = false (concurrent reads OK). +/// +/// This is a **minimal-footprint reference** (≈24 bytes). It holds only a SlotHandle +/// (token into SlotRegistry's central registry) and the resolved CryptoResourceId. +/// It does NOT copy the KeySlotConfig, hold a slot handler reference, or store key +/// material. All config/handler access goes through SlotRegistry via the handle. +/// +/// Two consumption models: +/// 1. Slot-direct: context calls LoadOrShare at operation time, which checks +/// the KeyRegistry and reuses an existing KeyDataNode when possible. +/// 2. Explicit LoadKey: user calls KEY_LOAD, producing a KeyDataNode in +/// the client tree referencing a KeyDataNode in the per-provider registry. +class KeySlotDataNode : public data_manager::DataNode +{ + public: + /// @param slot_handle Lightweight reference into SlotRegistry. + /// @param slot_registry Shared pointer to the central SlotRegistry. + KeySlotDataNode(SlotHandle slot_handle, SlotRegistry::Sptr slot_registry) + : DataNode(false), // exclusiveAccess = false → concurrent reads OK + m_slot_handle{slot_handle}, + m_slot_registry{std::move(slot_registry)} + { + } + + /// @brief Destructor — no-op with respect to SlotRegistry. + /// + /// Resolution left no footprint to clean up. + ~KeySlotDataNode() override = default; + + [[nodiscard]] data_manager::DataNodeType GetNodeType() const noexcept override + { + return data_manager::DataNodeType::kKeySlot; + } + + SlotHandle GetSlotHandle() const noexcept + { + return m_slot_handle; + } + + /// @brief Access config from central registry. + /// + /// Returns a pointer to the slot config stored in the registry. + /// The pointer is valid for the duration of the current operation. + [[nodiscard]] score::crypto::Expected + GetConfig() const + { + return m_slot_registry->GetConfig(m_slot_handle); + } + + /// @brief Access the SlotRegistry (for config reads). + SlotRegistry::Sptr GetSlotRegistry() const + { + return m_slot_registry; + } + + /// @brief Check whether a provider is allowed to access this slot. + /// + /// Delegates to `KeySlotConfig::IsProviderAllowed()`. Returns false if the + /// config is unavailable (fail-closed). + bool IsProviderAllowed(const common::ProviderId& provider_id) const + { + auto cfg_res = GetConfig(); + return cfg_res.has_value() && cfg_res.value()->IsProviderAllowed(provider_id); + } + + private: + SlotHandle m_slot_handle; ///< ~4 bytes: index into central registry + SlotRegistry::Sptr m_slot_registry; ///< shared_ptr to central registry +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_KEY_SLOT_DATA_NODE_HPP diff --git a/score/crypto/daemon/key_management/slot/access_policy_enforcer.cpp b/score/crypto/daemon/key_management/slot/access_policy_enforcer.cpp new file mode 100644 index 0000000..62a9cfd --- /dev/null +++ b/score/crypto/daemon/key_management/slot/access_policy_enforcer.cpp @@ -0,0 +1,108 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/slot/access_policy_enforcer.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" + +#include + +namespace score::crypto::daemon::key_management +{ + +score::crypto::Expected +AccessPolicyEnforcer::CheckSlotAccess(const KeySlotConfig& slot, data_manager::ClientId client_id) +{ + const uint32_t uid = control_plane::protocol::GetUidFromClientId(client_id); + + const auto& allowed = slot.access_policy.allowed_uids; + if (std::find(allowed.begin(), allowed.end(), uid) != allowed.end()) + { + return std::monostate{}; + } + + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAccessDenied); +} + +score::crypto::Expected +AccessPolicyEnforcer::CheckWritePermission(const KeySlotConfig& slot, data_manager::ClientId client_id) +{ + const uint32_t uid = control_plane::protocol::GetUidFromClientId(client_id); + + const auto& allowed = slot.access_policy.allowed_write_uids; + if (std::find(allowed.begin(), allowed.end(), uid) != allowed.end()) + { + return std::monostate{}; + } + + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAccessDenied); +} + +score::crypto::Expected +AccessPolicyEnforcer::CheckOperationPermission(const KeySlotConfig& slot, + score::mw::crypto::KeyOperationPermission requested_op) +{ + if (score::mw::crypto::HasPermission(slot.allowed_operations, requested_op)) + { + return std::monostate{}; + } + + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kKeyOperationNotPermitted); +} + +score::crypto::Expected AccessPolicyEnforcer::Authorize( + const KeySlotConfig& slot, + data_manager::ClientId client_id, + score::mw::crypto::KeyOperationPermission requested_op) +{ + auto access_result = CheckSlotAccess(slot, client_id); + if (!access_result.has_value()) + { + return access_result; + } + + // kNone means no specific operation check needed (e.g., ResolveResource) + if (requested_op != score::mw::crypto::KeyOperationPermission::kNone) + { + auto perm_result = CheckOperationPermission(slot, requested_op); + if (!perm_result.has_value()) + { + return perm_result; + } + } + + return std::monostate{}; +} + +score::crypto::Expected +AccessPolicyEnforcer::CheckProviderAccess(const KeySlotConfig& slot, + const common::ProviderId& provider_id, + bool is_write) +{ + if (is_write) + { + // Only the primary provider (index 0) may mutate the slot. + if (!slot.provider_ids.empty() && slot.provider_ids.front() == provider_id) + { + return std::monostate{}; + } + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAccessDenied); + } + + // Read / consume: any listed provider is accepted. + if (slot.IsProviderAllowed(provider_id)) + { + return std::monostate{}; + } + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAccessDenied); +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/slot/access_policy_enforcer.hpp b/score/crypto/daemon/key_management/slot/access_policy_enforcer.hpp new file mode 100644 index 0000000..cb0c70a --- /dev/null +++ b/score/crypto/daemon/key_management/slot/access_policy_enforcer.hpp @@ -0,0 +1,91 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_ACCESS_POLICY_ENFORCER_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_ACCESS_POLICY_ENFORCER_HPP + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" +#include "score/mw/crypto/api/common/types.hpp" + +#include + +namespace score::crypto::daemon::key_management +{ + +/// @brief Centralized access control and policy enforcement. +/// +/// All access decisions are made here — providers never implement access control. +/// Enforces UID-based access control, operation permission bitmasks, and resource +/// quotas. GID, application identity, and lifecycle state enforcement are +/// extension points. +class AccessPolicyEnforcer +{ + public: + /// @brief Check if client UID is in the slot's allowed_uids list. + /// + /// @param slot The slot configuration containing the access policy. + /// @param client_id Composite PID|UID of the requesting connection. + /// @return std::monostate on success, or kAccessDenied. + static score::crypto::Expected CheckSlotAccess( + const KeySlotConfig& slot, + data_manager::ClientId client_id); + + /// @brief Check if client UID is in the slot's allowed_write_uids list. + /// + /// Write access governs GenerateKey and key persist/import operations. + /// + /// @param slot The slot configuration containing the access policy. + /// @param client_id Composite PID|UID of the requesting connection. + /// @return std::monostate on success, or kAccessDenied. + static score::crypto::Expected CheckWritePermission( + const KeySlotConfig& slot, + data_manager::ClientId client_id); + + /// @brief Check if the requested operation is in the slot's allowed_operations. + /// + /// @param slot The slot configuration containing the permission set. + /// @param requested_op The operation being requested. + /// @return std::monostate on success, or kKeyOperationNotPermitted. + static score::crypto::Expected + CheckOperationPermission(const KeySlotConfig& slot, score::mw::crypto::KeyOperationPermission requested_op); + + /// @brief Combined: access + operation check. + /// + /// @param slot The slot configuration. + /// @param client_id Composite PID|UID. + /// @param requested_op The operation being requested. + /// @return std::monostate on success, or the first error encountered. + static score::crypto::Expected Authorize( + const KeySlotConfig& slot, + data_manager::ClientId client_id, + score::mw::crypto::KeyOperationPermission requested_op); + + /// @brief Check that `provider_id` is allowed to use this slot. + /// + /// For write operations (`is_write == true`), only the primary provider + /// (`slot.provider_ids[0]`) is accepted. For read operations, any provider + /// listed in `slot.provider_ids` is accepted. + /// + /// @param slot The slot configuration containing the provider list. + /// @param provider_id The provider requesting access. + /// @param is_write True for mutating operations (generate, store, delete). + /// @return std::monostate on success, or kAccessDenied if the provider is not permitted. + static score::crypto::Expected + CheckProviderAccess(const KeySlotConfig& slot, const common::ProviderId& provider_id, bool is_write); +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_ACCESS_POLICY_ENFORCER_HPP diff --git a/score/crypto/daemon/key_management/slot/config_driven_slot_catalog.cpp b/score/crypto/daemon/key_management/slot/config_driven_slot_catalog.cpp new file mode 100644 index 0000000..cf75257 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/config_driven_slot_catalog.cpp @@ -0,0 +1,132 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/slot/config_driven_slot_catalog.hpp" + +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" +#include "score/crypto/daemon/key_management/slot/slot_registry.hpp" + +#include + +namespace score::crypto::daemon::key_management +{ + +namespace +{ + +/// @brief Convert the string representation of allowed_operations to the bitmask. +/// +/// Supports: "MAC", "ENCRYPT", "DECRYPT", "SIGN", "VERIFY", "ALL", "NONE". +/// Pipe-separated combinations: "ENCRYPT|DECRYPT". +score::mw::crypto::KeyOperationPermission ParseOperationPermission(const std::string& ops_str) +{ + if (ops_str.empty() || ops_str == "NONE") + { + return score::mw::crypto::KeyOperationPermission::kNone; + } + if (ops_str == "ALL") + { + return score::mw::crypto::KeyOperationPermission::kAll; + } + + auto result = score::mw::crypto::KeyOperationPermission::kNone; + + // Simple pipe-separated token parsing + std::string token; + for (std::size_t i = 0U; i <= ops_str.size(); ++i) + { + if (i == ops_str.size() || ops_str[i] == '|') + { + if (token == "MAC") + { + result = static_cast( + static_cast(result) | + static_cast(score::mw::crypto::KeyOperationPermission::kMac)); + } + else if (token == "ENCRYPT") + { + result = static_cast( + static_cast(result) | + static_cast(score::mw::crypto::KeyOperationPermission::kEncrypt)); + } + else if (token == "DECRYPT") + { + result = static_cast( + static_cast(result) | + static_cast(score::mw::crypto::KeyOperationPermission::kDecrypt)); + } + else if (token == "SIGN") + { + result = static_cast( + static_cast(result) | + static_cast(score::mw::crypto::KeyOperationPermission::kSign)); + } + else if (token == "VERIFY") + { + result = static_cast( + static_cast(result) | + static_cast(score::mw::crypto::KeyOperationPermission::kVerify)); + } + token.clear(); + } + else + { + token += ops_str[i]; + } + } + + return result; +} + +} // namespace + +ConfigDrivenSlotCatalog::ConfigDrivenSlotCatalog(const config::KeyConfig& key_config) : m_key_config{key_config} {} + +void ConfigDrivenSlotCatalog::Load(SlotRegistry& registry) +{ + const auto& entries = m_key_config.GetSlotEntries(); + + for (const auto& entry : entries) + { + KeySlotConfig config{}; + config.slot_name = entry.slot_name; + config.algorithm = entry.algorithm; + // Store provider names as-is from config (strings); numeric IDs will be resolved later + config.provider_names = entry.provider_names; + config.allowed_operations = ParseOperationPermission(entry.allowed_operations); + + // Access policy + config.access_policy.allowed_uids = entry.allowed_uids; + config.access_policy.allowed_write_uids = entry.allowed_write_uids; + + // Deployment descriptor — each IKeySlotHandler loads provider-specific + // key properties from this file at runtime. + config.deployment_path = entry.deployment_path; + config.deployment_format = entry.deployment_format; + + registry.RegisterSlot(std::move(config)); + + std::cout << LOG_PREFIX << "Registered slot '" << entry.slot_name + << "' (primary_provider=" << (entry.provider_names.empty() ? "" : entry.provider_names[0]) + << ", algorithm=" << entry.algorithm << ")\n"; + } + + std::cout << LOG_PREFIX << "Loaded " << entries.size() << " slot(s) from configuration.\n"; + + // Register per-application resource ID mappings. + for (const auto& mapping : m_key_config.GetAppResourceEntries()) + { + registry.RegisterAppResource(mapping.uid, mapping.app_resource_id, mapping.slot_name); + } +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/slot/config_driven_slot_catalog.hpp b/score/crypto/daemon/key_management/slot/config_driven_slot_catalog.hpp new file mode 100644 index 0000000..87a170c --- /dev/null +++ b/score/crypto/daemon/key_management/slot/config_driven_slot_catalog.hpp @@ -0,0 +1,63 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_CONFIG_DRIVEN_SLOT_CATALOG_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_CONFIG_DRIVEN_SLOT_CATALOG_HPP + +#include "score/crypto/daemon/config/inc/config.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_slot_catalog.hpp" + +#include + +namespace score::crypto::daemon::key_management +{ + +/// @brief IKeySlotCatalog that reads key slot definitions from the daemon's +/// parsed configuration (KeyConfig). +/// +/// This is the **production catalog**. It converts each `KeyConfig::KeySlotEntry` +/// into a `KeySlotConfig` and calls `registry.RegisterSlot()`. +/// +/// The configuration source (JSON file, flatbuffer, environment) is parsed +/// upstream by Config::ParseConfig(). This catalog is format-agnostic — it only +/// reads the already-parsed `KeySlotEntry` vector from `KeyConfig`. +/// +/// ### PKCS#11 slot integration +/// An integrator adds PKCS#11 key slot entries to the configuration manifest +/// with a `deployment_path` pointing to a kv-format deployment descriptor. +/// The descriptor's `[key]` section carries PKCS#11 identifiers: +/// - `pkcs11.label` → CKA_LABEL +/// - `pkcs11.object_id` → CKA_ID (hex string) +/// - `pkcs11.object_class` → "secret_key", "private_key", etc. +/// +/// The catalog stores the deployment_path unchanged — the PKCS#11 slot handler +/// loads and interprets the descriptor at LoadKey time. +/// +class ConfigDrivenSlotCatalog final : public IKeySlotCatalog +{ + public: + /// @param key_config Reference to the parsed key configuration section. + explicit ConfigDrivenSlotCatalog(const config::KeyConfig& key_config); + ~ConfigDrivenSlotCatalog() override = default; + + /// @copydoc IKeySlotCatalog::Load + void Load(SlotRegistry& registry) override; + + private: + const config::KeyConfig& m_key_config; + + static constexpr std::string_view LOG_PREFIX = "[CONFIG_DRIVEN_CATALOG] "; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_CONFIG_DRIVEN_SLOT_CATALOG_HPP diff --git a/score/crypto/daemon/key_management/slot/deployment/BUILD b/score/crypto/daemon/key_management/slot/deployment/BUILD new file mode 100644 index 0000000..483b721 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/BUILD @@ -0,0 +1,48 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +# Header-only: interfaces + shared path validation utility. +# No format-specific deps — adding a new format (json, flatbuffer, ...) only +# requires adding a new cc_library target in this file; nothing else changes. +cc_library( + name = "deployment_iface", + hdrs = [ + "deployment_path_utils.hpp", + "i_deployment_loader.hpp", + "i_deployment_writer.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/common:common_types", + "//score/crypto/daemon/common", + "//score/crypto/daemon/key_management:key_management_headers", + ], +) + +# KV format implementation. Stdlib-only — no extra library deps required. +# To add a new format, create an analogous target here (e.g. json_deployment) +# and add it as a dep to //score/crypto/daemon/key_management:key_management. +cc_library( + name = "kv_deployment", + srcs = [ + "kv/kv_deployment_loader.cpp", + "kv/kv_deployment_writer.cpp", + ], + hdrs = [ + "kv/kv_deployment_loader.hpp", + "kv/kv_deployment_writer.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [":deployment_iface"], +) diff --git a/score/crypto/daemon/key_management/slot/deployment/deployment_path_utils.hpp b/score/crypto/daemon/key_management/slot/deployment/deployment_path_utils.hpp new file mode 100644 index 0000000..b42ada2 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/deployment_path_utils.hpp @@ -0,0 +1,51 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_DEPLOYMENT_PATH_UTILS_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_DEPLOYMENT_PATH_UTILS_HPP + +#include + +namespace score::crypto::daemon::key_management +{ + +/// @brief Returns true if the path is absolute and contains no ".." path traversal components. +/// +/// Used as a single shared security pre-check by DeploymentLoaderFactory and +/// DeploymentWriterFactory before dispatching to a format-specific implementation. +/// Centralises path validation so it is not duplicated across every format class. +[[nodiscard]] inline bool IsDeploymentPathSafe(const std::string& path) noexcept +{ + if (path.empty()) + { + return false; + } + + // Require absolute path (Unix: starts with '/'). + const bool is_absolute = (path[0] == '/') || (path.size() >= 3U && path[1] == ':'); + if (!is_absolute) + { + return false; + } + + // Reject path traversal: ".." as a standalone component. + if (path.find("..") != std::string::npos) + { + return false; + } + + return true; +} + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_DEPLOYMENT_PATH_UTILS_HPP diff --git a/score/crypto/daemon/key_management/slot/deployment/i_deployment_loader.hpp b/score/crypto/daemon/key_management/slot/deployment/i_deployment_loader.hpp new file mode 100644 index 0000000..2b99010 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/i_deployment_loader.hpp @@ -0,0 +1,53 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_I_DEPLOYMENT_LOADER_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_I_DEPLOYMENT_LOADER_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" + +#include + +namespace score::crypto::daemon::key_management +{ + +/// @brief Interface for format-specific deployment descriptor loaders. +/// +/// Each concrete implementation handles exactly one serialization format +/// (kv, json, flatbuffer, custom, ...). The path safety pre-check is +/// performed by DeploymentLoaderFactory *before* calling Load(), so +/// implementations can assume a valid, absolute, traversal-free path. +/// +/// ### Adding a new format +/// 1. Implement this interface in a new class (e.g., `JsonDeploymentLoader`). +/// 2. Place the class under `slot/deployment//`. +/// 3. Register it in `DeploymentLoaderFactory::Create()`. +/// No other files need to change. +class IDeploymentLoader +{ + public: + virtual ~IDeploymentLoader() = default; + + /// @brief Load a SlotDeploymentInfo from the given (pre-validated) path. + /// + /// @param path Absolute path to the deployment descriptor file. The caller + /// (factory) has already verified it is safe. + /// @return Parsed SlotDeploymentInfo on success, or DaemonErrorCode on failure. + [[nodiscard]] virtual score::crypto::Expected + Load(const std::string& path) = 0; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_I_DEPLOYMENT_LOADER_HPP diff --git a/score/crypto/daemon/key_management/slot/deployment/i_deployment_writer.hpp b/score/crypto/daemon/key_management/slot/deployment/i_deployment_writer.hpp new file mode 100644 index 0000000..2cf5bc5 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/i_deployment_writer.hpp @@ -0,0 +1,54 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_I_DEPLOYMENT_WRITER_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_I_DEPLOYMENT_WRITER_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" + +#include +#include + +namespace score::crypto::daemon::key_management +{ + +/// @brief Interface for format-specific deployment descriptor writers. +/// +/// Mirrors IDeploymentLoader: one concrete class per serialization format. +/// The path safety pre-check is performed by DeploymentWriterFactory *before* +/// calling Write(), so implementations can assume a valid path. +/// +/// ### Adding a new format +/// 1. Implement this interface in a new class (e.g., `JsonDeploymentWriter`). +/// 2. Place the class under `slot/deployment//`. +/// 3. Register it in `DeploymentWriterFactory::Create()`. +class IDeploymentWriter +{ + public: + virtual ~IDeploymentWriter() = default; + + /// @brief Write a SlotDeploymentInfo to the given (pre-validated) path. + /// + /// @param path Absolute path to the deployment descriptor file. The caller + /// (factory) has already verified it is safe. + /// @param info The deployment info to persist. + /// @return std::monostate on success, or DaemonErrorCode on failure. + [[nodiscard]] virtual score::crypto::Expected Write( + const std::string& path, + const SlotDeploymentInfo& info) = 0; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_I_DEPLOYMENT_WRITER_HPP diff --git a/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.cpp b/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.cpp new file mode 100644 index 0000000..c5c5dab --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.cpp @@ -0,0 +1,118 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.hpp" + +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::key_management +{ + +score::crypto::Expected KvDeploymentLoader::Load( + const std::string& path) +{ + std::ifstream file(path); + if (!file.is_open()) + { + std::cerr << kLogPrefix << "Cannot open deployment descriptor: " << path << '\n'; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + SlotDeploymentInfo info{}; + + enum class Section : uint8_t + { + kMetadata, + kKey + }; + auto current_section = Section::kMetadata; + + std::string line; + while (std::getline(file, line)) + { + // Trim leading/trailing whitespace. + std::size_t start = line.find_first_not_of(" \t\r\n"); + if (start == std::string::npos) + { + continue; // blank line + } + std::size_t end = line.find_last_not_of(" \t\r\n"); + line = line.substr(start, end - start + 1U); + + // Skip comments. + if (line[0] == '#') + { + continue; + } + + // Section headers. + if (line == "[metadata]") + { + current_section = Section::kMetadata; + continue; + } + if (line == "[key]") + { + current_section = Section::kKey; + continue; + } + + // Parse key=value pairs. + const auto eq_pos = line.find('='); + if (eq_pos == std::string::npos) + { + continue; // malformed line — skip + } + + std::string key = line.substr(0U, eq_pos); + std::string value = line.substr(eq_pos + 1U); + + // Trim key and value. + auto trim = [](std::string& s) { + std::size_t s_start = s.find_first_not_of(" \t"); + std::size_t s_end = s.find_last_not_of(" \t"); + if (s_start == std::string::npos) + { + s.clear(); + } + else + { + s = s.substr(s_start, s_end - s_start + 1U); + } + }; + trim(key); + trim(value); + + if (key.empty()) + { + continue; + } + + switch (current_section) + { + case Section::kMetadata: + info.metadata[std::move(key)] = std::move(value); + break; + case Section::kKey: + info.key_properties[std::move(key)] = std::move(value); + break; + } + } + + return info; +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.hpp b/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.hpp new file mode 100644 index 0000000..4b98fef --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.hpp @@ -0,0 +1,54 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_KV_KV_DEPLOYMENT_LOADER_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_KV_KV_DEPLOYMENT_LOADER_HPP + +#include "score/crypto/daemon/key_management/slot/deployment/i_deployment_loader.hpp" + +#include + +namespace score::crypto::daemon::key_management +{ + +/// @brief Loads a SlotDeploymentInfo from a key=value text file. +/// +/// File format: +/// @code +/// # comment +/// [metadata] +/// availability = active +/// provisioned_at = 2025-11-03T08:42:00Z +/// +/// [key] +/// key_path = /etc/crypto/keys/hmac.bin +/// key_format = raw +/// @endcode +/// +/// - Lines starting with `#` are comments and are ignored. +/// - Blank lines are ignored. +/// - Section headers `[metadata]` and `[key]` switch the active map. +/// - Lines without a `=` separator are silently skipped. +/// - Keys and values are whitespace-trimmed. +class KvDeploymentLoader : public IDeploymentLoader +{ + public: + [[nodiscard]] score::crypto::Expected Load( + const std::string& path) override; + + private: + static constexpr std::string_view kLogPrefix = "[KV_DEPLOYMENT_LOADER] "; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_KV_KV_DEPLOYMENT_LOADER_HPP diff --git a/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.cpp b/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.cpp new file mode 100644 index 0000000..d784e43 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.cpp @@ -0,0 +1,54 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.hpp" + +#include +#include +#include + +namespace score::crypto::daemon::key_management +{ + +score::crypto::Expected KvDeploymentWriter::Write( + const std::string& path, + const SlotDeploymentInfo& info) +{ + std::ofstream file(path, std::ios::trunc); + if (!file.is_open()) + { + std::cerr << kLogPrefix << "Cannot open deployment descriptor for writing: " << path << '\n'; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + file << "[metadata]\n"; + for (const auto& [key, value] : info.metadata) + { + file << key << '=' << value << '\n'; + } + + file << "\n[key]\n"; + for (const auto& [key, value] : info.key_properties) + { + file << key << '=' << value << '\n'; + } + + if (!file.good()) + { + std::cerr << kLogPrefix << "Write error for deployment descriptor: " << path << '\n'; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + return std::monostate{}; +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.hpp b/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.hpp new file mode 100644 index 0000000..aa74785 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.hpp @@ -0,0 +1,44 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_KV_KV_DEPLOYMENT_WRITER_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_KV_KV_DEPLOYMENT_WRITER_HPP + +#include "score/crypto/daemon/key_management/slot/deployment/i_deployment_writer.hpp" + +#include + +namespace score::crypto::daemon::key_management +{ + +/// @brief Writes a SlotDeploymentInfo to a key=value text file. +/// +/// Produces the same `[metadata]` / `[key]` section format that +/// KvDeploymentLoader can read back. Existing file content is replaced +/// (opened with `std::ios::trunc`). +/// +/// @note Writes are not currently atomic. A future extension may implement +/// write-then-rename to protect against partial writes on crash. +class KvDeploymentWriter : public IDeploymentWriter +{ + public: + [[nodiscard]] score::crypto::Expected Write( + const std::string& path, + const SlotDeploymentInfo& info) override; + + private: + static constexpr std::string_view kLogPrefix = "[KV_DEPLOYMENT_WRITER] "; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_DEPLOYMENT_KV_KV_DEPLOYMENT_WRITER_HPP diff --git a/score/crypto/daemon/key_management/slot/deployment_loader.cpp b/score/crypto/daemon/key_management/slot/deployment_loader.cpp new file mode 100644 index 0000000..07c9949 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment_loader.cpp @@ -0,0 +1,45 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/slot/deployment_loader.hpp" + +#include "score/crypto/daemon/key_management/slot/deployment/deployment_path_utils.hpp" +#include "score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.hpp" + +#include +#include + +namespace score::crypto::daemon::key_management +{ + +score::crypto::Expected DeploymentLoader::Load( + const std::string& path, + const std::string& format) +{ + if (!IsDeploymentPathSafe(path)) + { + std::cerr << LOG_PREFIX << "Unsafe deployment path rejected: " << path << '\n'; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + if (format == "kv") + { + return KvDeploymentLoader{}.Load(path); + } + // To add a new format: include its header above and add a branch here. + // Example: if (format == "json") { return JsonDeploymentLoader{}.Load(path); } + + std::cerr << LOG_PREFIX << "Unsupported deployment format: " << format << '\n'; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/slot/deployment_loader.hpp b/score/crypto/daemon/key_management/slot/deployment_loader.hpp new file mode 100644 index 0000000..864a431 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment_loader.hpp @@ -0,0 +1,69 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_DEPLOYMENT_LOADER_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_DEPLOYMENT_LOADER_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" + +#include +#include + +namespace score::crypto::daemon::key_management +{ + +/// @brief Loads dynamic slot and key information from a deployment descriptor. +/// +/// The deployment descriptor is an external file (or folder) referenced by +/// `KeySlotConfig::deployment_path`. Its content is format-dependent +/// (`KeySlotConfig::deployment_format`): +/// +/// - `"kv"` (default): Simple key=value text file. Lines of the form +/// `key=value`, blank lines ignored, `#` comments supported. +/// Sections `[metadata]` and `[key]` separate the two maps. +/// +/// - `"json"`: Reserved for future use (requires JSON library dependency). +/// +/// - `"bin"`: Reserved for future use (binary/flatbuffer encoding). +/// +/// ### Security +/// +/// - The file path must be absolute (no relative components). +/// - Path traversal (`..`) is rejected. +/// - The loader does not follow symlinks (defence-in-depth; OS-level +/// enforcement may vary). +/// +/// ### Thread safety +/// +/// `Load()` is stateless and may be called concurrently from multiple threads +/// for different deployment paths. Concurrent reads of the same path are safe; +/// concurrent read + write requires external synchronisation (see DeploymentWriter). +class DeploymentLoader +{ + public: + /// @brief Load a deployment descriptor from the given path and format. + /// + /// @param path Absolute path to the deployment descriptor file. + /// @param format Format hint: "kv", "json", "bin". + /// @return Parsed SlotDeploymentInfo on success, or DaemonErrorCode on failure. + [[nodiscard]] static score::crypto::Expected + Load(const std::string& path, const std::string& format); + + private: + static constexpr std::string_view LOG_PREFIX = "[DEPLOYMENT_LOADER] "; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_DEPLOYMENT_LOADER_HPP diff --git a/score/crypto/daemon/key_management/slot/deployment_writer.cpp b/score/crypto/daemon/key_management/slot/deployment_writer.cpp new file mode 100644 index 0000000..a4a95a6 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment_writer.cpp @@ -0,0 +1,44 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/slot/deployment_writer.hpp" + +#include "score/crypto/daemon/key_management/slot/deployment/deployment_path_utils.hpp" +#include "score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.hpp" + +#include +#include + +namespace score::crypto::daemon::key_management +{ + +score::crypto::Expected +DeploymentWriter::Write(const std::string& path, const std::string& format, const SlotDeploymentInfo& info) +{ + if (!IsDeploymentPathSafe(path)) + { + std::cerr << LOG_PREFIX << "Unsafe deployment path rejected: " << path << '\n'; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + if (format == "kv") + { + return KvDeploymentWriter{}.Write(path, info); + } + // To add a new format: include its header above and add a branch here. + // Example: if (format == "json") { return JsonDeploymentWriter{}.Write(path, info); } + + std::cerr << LOG_PREFIX << "Unsupported deployment format: " << format << '\n'; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/slot/deployment_writer.hpp b/score/crypto/daemon/key_management/slot/deployment_writer.hpp new file mode 100644 index 0000000..adc557d --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment_writer.hpp @@ -0,0 +1,60 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_DEPLOYMENT_WRITER_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_DEPLOYMENT_WRITER_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" + +#include +#include +#include + +namespace score::crypto::daemon::key_management +{ + +/// @brief Writes dynamic slot and key information to a deployment descriptor. +/// +/// Used during key update operations (generate-to-slot, import-to-slot) to +/// persist new key properties back to the deployment path. +/// +/// ### Atomicity (future extension) +/// +/// The current implementation writes directly to the target path. A future +/// extension may implement atomic write (write to temp file, then rename) +/// to avoid partial writes on crash. +/// +/// ### Thread safety +/// +/// Concurrent writes to the same deployment path require external +/// synchronisation (e.g., a per-slot mutex in the registry). +class DeploymentWriter +{ + public: + /// @brief Write a SlotDeploymentInfo to the given path in the specified format. + /// + /// @param path Absolute path to the deployment descriptor file. + /// @param format Format hint: "kv", "json", "bin". + /// @param info The deployment info to write. + /// @return std::monostate on success, or DaemonErrorCode on failure. + [[nodiscard]] static score::crypto::Expected + Write(const std::string& path, const std::string& format, const SlotDeploymentInfo& info); + + private: + static constexpr std::string_view LOG_PREFIX = "[DEPLOYMENT_WRITER] "; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_DEPLOYMENT_WRITER_HPP diff --git a/score/crypto/daemon/key_management/slot/file_backed_slot_handler.cpp b/score/crypto/daemon/key_management/slot/file_backed_slot_handler.cpp new file mode 100644 index 0000000..8bb1cd9 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/file_backed_slot_handler.cpp @@ -0,0 +1,132 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/slot/file_backed_slot_handler.hpp" + +#include "score/crypto/daemon/common/secure_memory.hpp" +#include "score/crypto/daemon/key_management/detail/slot_info_builder.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_management_operations.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" +#include "score/crypto/daemon/key_management/slot/deployment_loader.hpp" + +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::key_management +{ + +FileBackedSlotHandler::FileBackedSlotHandler(IKeyFactory::Sptr factory) : m_factory{std::move(factory)} {} + +score::crypto::Expected +FileBackedSlotHandler::LoadKey(const KeySlotConfig& slot) +{ + // Load deployment info to get the key file path. + auto deploy_result = DeploymentLoader::Load(slot.deployment_path, slot.deployment_format); + if (!deploy_result.has_value()) + { + return score::crypto::make_unexpected(deploy_result.error()); + } + + const auto& deploy_info = deploy_result.value(); + const auto path_it = deploy_info.key_properties.find(std::string{deployment_keys::kKeyPath}); + if ((path_it == deploy_info.key_properties.end()) || path_it->second.empty()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + auto read_result = ReadKeyFile(path_it->second); + if (!read_result.has_value()) + { + return score::crypto::make_unexpected(read_result.error()); + } + + auto& buffer = read_result.value(); + + KeyImportRequest req{}; + req.key_data = buffer.data(); + req.key_data_size = buffer.size(); + req.algorithm = slot.algorithm; + req.permissions = slot.allowed_operations; + + auto import_result = m_factory->ImportKey(req); + + // Securely zeroize regardless of import outcome. + common::SecureZeroizeAndClear(buffer); + + return import_result; +} + +score::crypto::Expected +FileBackedSlotHandler::GetSlotState(const KeySlotConfig& slot) +{ + auto deploy_result = DeploymentLoader::Load(slot.deployment_path, slot.deployment_format); + if (!deploy_result.has_value()) + { + return score::mw::crypto::KeySlotState::kEmpty; + } + + const auto& deploy_info = deploy_result.value(); + const auto path_it = deploy_info.key_properties.find(std::string{deployment_keys::kKeyPath}); + if ((path_it == deploy_info.key_properties.end()) || path_it->second.empty()) + { + return score::mw::crypto::KeySlotState::kEmpty; + } + + std::ifstream file(path_it->second, std::ios::binary); + if (file.good()) + { + return score::mw::crypto::KeySlotState::kOccupied; + } + + return score::mw::crypto::KeySlotState::kEmpty; +} + +score::crypto::Expected +FileBackedSlotHandler::GetSlotInfo(const KeySlotConfig& slot) +{ + auto state_result = GetSlotState(slot); + if (!state_result.has_value()) + { + return score::crypto::make_unexpected(state_result.error()); + } + return detail::BuildKeySlotInfo(slot, state_result.value()); +} + +score::crypto::Expected, score::crypto::daemon::common::DaemonErrorCode> +FileBackedSlotHandler::ReadKeyFile(const std::string& file_path) const +{ + std::ifstream file(file_path, std::ios::binary); + if (!file.is_open()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kKeySlotEmpty); + } + + std::vector buffer((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + if (buffer.empty()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + if (buffer.size() > kMaxKeyFileSize) + { + common::SecureZeroizeAndClear(buffer); + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + return buffer; +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/slot/file_backed_slot_handler.hpp b/score/crypto/daemon/key_management/slot/file_backed_slot_handler.hpp new file mode 100644 index 0000000..74156da --- /dev/null +++ b/score/crypto/daemon/key_management/slot/file_backed_slot_handler.hpp @@ -0,0 +1,87 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_FILE_BACKED_SLOT_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_FILE_BACKED_SLOT_HANDLER_HPP + +#include "score/crypto/daemon/key_management/interfaces/i_key_factory.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp" + +#include +#include +#include +#include + +namespace score::crypto::daemon::key_management +{ + +/// IKeySlotHandler for file-backed (pure-SW) providers. +/// +/// Reads raw key bytes from a file and delegates to IKeyFactory::ImportKey(). +/// The local read buffer is securely zeroized immediately after the provider +/// has internalized the material, even on failure. +/// +/// Support for encrypted-at-rest files is reserved for future extension. +/// +/// One FileBackedSlotHandler is shared across all slots that use the same +/// provider. Per-call slot configuration (file path, algorithm, permissions) +/// comes from the KeySlotConfig argument. +class FileBackedSlotHandler final : public IKeySlotHandler +{ + public: + /// @param factory Provider key factory used for ImportKey. + explicit FileBackedSlotHandler(IKeyFactory::Sptr factory); + + ~FileBackedSlotHandler() override = default; + + FileBackedSlotHandler(const FileBackedSlotHandler&) = delete; + FileBackedSlotHandler& operator=(const FileBackedSlotHandler&) = delete; + FileBackedSlotHandler(FileBackedSlotHandler&&) = delete; + FileBackedSlotHandler& operator=(FileBackedSlotHandler&&) = delete; + + /// Load key from file → ImportKey → zeroize local buffer. + /// + /// Steps: + /// 1. Load deployment descriptor from slot.deployment_path. + /// 2. Resolve file path from key_properties[kKeyPath]. + /// 3. Read key bytes into a local vector. + /// 4. Validate: size > 0 and <= kMaxKeyFileSize. + /// 5. Delegate to IKeyFactory::ImportKey(KeyImportRequest). + /// 6. Securely zeroize and clear the local buffer (always, even on error). + [[nodiscard]] score::crypto::Expected LoadKey( + const KeySlotConfig& slot) override; + + /// Return kOccupied if the key file exists, kEmpty otherwise. + [[nodiscard]] score::crypto::Expected + GetSlotState(const KeySlotConfig& slot) override; + + /// Return slot metadata derived from the KeySlotConfig. + [[nodiscard]] score::crypto::Expected + GetSlotInfo(const KeySlotConfig& slot) override; + + private: + [[nodiscard]] score::crypto::Expected, score::crypto::daemon::common::DaemonErrorCode> + ReadKeyFile(const std::string& file_path) const; + + IKeyFactory::Sptr m_factory; + + /// Maximum key file size to guard against reading unreasonably large files. + static constexpr std::size_t kMaxKeyFileSize = 8U * 1024U; + + static constexpr std::string_view LOG_PREFIX = "[FILE_BACKED_SLOT_HANDLER] "; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_FILE_BACKED_SLOT_HANDLER_HPP diff --git a/score/crypto/daemon/key_management/slot/slot_registry.cpp b/score/crypto/daemon/key_management/slot/slot_registry.cpp new file mode 100644 index 0000000..d20ee13 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/slot_registry.cpp @@ -0,0 +1,176 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/key_management/slot/slot_registry.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/daemon/key_management/slot/access_policy_enforcer.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" + +#include +#include + +namespace score::crypto::daemon::key_management +{ + +SlotHandle SlotRegistry::RegisterSlot(KeySlotConfig config) +{ + std::lock_guard lock(m_mutex); + const std::string name = config.slot_name; + + if (m_name_index.find(name) != m_name_index.end()) + { + std::cerr << LOG_PREFIX << "Duplicate slot name ignored: '" << name << "'\n"; + return SlotHandle{}; + } + + const auto index = static_cast(m_registry.size()); + + SlotRegistryEntry entry{}; + entry.config = std::move(config); + + m_name_index[name] = index; + m_registry.push_back(std::move(entry)); + return SlotHandle{index}; +} + +score::crypto::Expected SlotRegistry::ResolveSlot( + const std::string& slot_name, + data_manager::ClientId client_id) const +{ + auto it = m_name_index.find(slot_name); + if (it == m_name_index.end()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + + const auto index = it->second; + const auto& entry = m_registry[index]; + + auto access_result = AccessPolicyEnforcer::CheckSlotAccess(entry.config, client_id); + if (!access_result.has_value()) + { + return score::crypto::make_unexpected(access_result.error()); + } + + return SlotHandle{index}; +} + +score::crypto::Expected SlotRegistry::GetConfig( + SlotHandle handle) const +{ + if (!IsValidHandle(handle)) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + return &m_registry[handle.index].config; +} + +std::size_t SlotRegistry::GetSlotCount() const noexcept +{ + return m_registry.size(); +} + +void SlotRegistry::RegisterAppResource(uint32_t uid, const std::string& app_resource_id, const std::string& slot_name) +{ + std::lock_guard lock(m_mutex); + auto& uid_map = m_app_resource_map[uid]; + if (uid_map.find(app_resource_id) != uid_map.end()) + { + std::cerr << LOG_PREFIX << "Duplicate app resource mapping ignored: uid=" << uid << " resource='" + << app_resource_id << "'\n"; + return; + } + uid_map[app_resource_id] = slot_name; +} + +score::crypto::Expected SlotRegistry::ResolveAppResource( + const std::string& app_resource_id, + data_manager::ClientId client_id) const +{ + const uint32_t uid = control_plane::protocol::GetUidFromClientId(client_id); + + std::string slot_name; + { + std::lock_guard lock(m_mutex); + auto uid_it = m_app_resource_map.find(uid); + if (uid_it == m_app_resource_map.end()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + auto res_it = uid_it->second.find(app_resource_id); + if (res_it == uid_it->second.end()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + slot_name = res_it->second; + } + + return ResolveSlot(slot_name, client_id); +} + +void SlotRegistry::ResolveProviderIds(const provider::ProviderManager& provider_manager) +{ + std::lock_guard lock(m_mutex); + for (auto& entry : m_registry) + { + entry.config.provider_ids.clear(); + for (const auto& name : entry.config.provider_names) + { + auto provider = provider_manager.GetProvider(name); + if (provider) + { + entry.config.provider_ids.push_back(provider->GetProviderId()); + } + else + { + std::cerr << LOG_PREFIX << "Warning: provider '" << name << "' not found for slot '" + << entry.config.slot_name << "'\n"; + } + } + } +} + +bool SlotRegistry::IsValidHandle(SlotHandle handle) const noexcept +{ + return handle.IsValid() && handle.index < static_cast(m_registry.size()); +} + +score::crypto::Expected +SlotRegistry::GetPrimaryProviderId(SlotHandle handle) const +{ + if (!IsValidHandle(handle)) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + const auto& ids = m_registry[handle.index].config.provider_ids; + if (ids.empty()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + return ids.front(); +} + +score::crypto::Expected +SlotRegistry::IsProviderAllowedForSlot(SlotHandle handle, const common::ProviderId& provider_id) const +{ + if (!IsValidHandle(handle)) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + if (m_registry[handle.index].config.IsProviderAllowed(provider_id)) + { + return std::monostate{}; + } + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAccessDenied); +} + +} // namespace score::crypto::daemon::key_management diff --git a/score/crypto/daemon/key_management/slot/slot_registry.hpp b/score/crypto/daemon/key_management/slot/slot_registry.hpp new file mode 100644 index 0000000..80c10e3 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/slot_registry.hpp @@ -0,0 +1,242 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_REGISTRY_HPP +#define SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_REGISTRY_HPP + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" +#include "score/mw/crypto/api/common/types.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::provider +{ +class ProviderManager; // Forward declaration +} // namespace score::crypto::daemon::provider + +namespace score::crypto::daemon::key_management +{ + +/// @brief Lightweight token referencing a slot entry in SlotRegistry. +/// +/// Cheap to copy (≈8 bytes). Used by KeySlotDataNode instead of copying +/// KeySlotConfig. The index refers to SlotRegistry's internal registry +/// vector. UINT32_MAX indicates an invalid/uninitialized handle. +struct SlotHandle +{ + uint32_t index{UINT32_MAX}; + + bool IsValid() const noexcept + { + return index != UINT32_MAX; + } + + bool operator==(const SlotHandle& other) const noexcept + { + return index == other.index; + } + + bool operator!=(const SlotHandle& other) const noexcept + { + return !(*this == other); + } +}; + +/// @brief Internal registry entry for a key slot. Owned by SlotRegistry. +/// +/// Not exposed to data nodes — accessed only through SlotRegistry's API. +struct SlotRegistryEntry +{ + KeySlotConfig config; ///< The full config — exists ONCE, shared across all contexts +}; + +// --------------------------------------------------------------------------- +// KeyRegistrationParams — common parameters for key registration operations +// --------------------------------------------------------------------------- + +/// Groups the stable identity/location parameters shared by +/// RegisterKeyMaterial and LoadOrShare, reducing their argument counts. +struct KeyRegistrationParams +{ + data_manager::ClientId client_id{0U}; + data_manager::DataNodeId parent_id{0U}; + common::ProviderId provider_id{common::kInvalidProviderId}; + SlotHandle slot_handle{}; ///< Non-default only when loading from a slot. +}; + +/// @brief Central registry for all key slot configurations. +/// +/// Single source of truth for slot configs, slot handlers, and per-slot state. +/// Per-connection KeySlotDataNodes hold only a lightweight SlotHandle (≈8 bytes) +/// referencing entries in this registry — they do NOT copy KeySlotConfig. +/// +/// Thread safety: The registry is protected by a mutex for state mutations. +/// Config reads are safe without locking after initialization (configs are +/// immutable after setup). +class SlotRegistry : public std::enable_shared_from_this +{ + public: + using Sptr = std::shared_ptr; + + SlotRegistry() = default; + ~SlotRegistry() = default; + + // Non-copyable, non-movable (shared via Sptr) + SlotRegistry(const SlotRegistry&) = delete; + SlotRegistry& operator=(const SlotRegistry&) = delete; + SlotRegistry(SlotRegistry&&) = delete; + SlotRegistry& operator=(SlotRegistry&&) = delete; + + /// @brief Register a key slot with the registry. + /// + /// Called by IKeySlotCatalog::Load() at startup, or dynamically when a new + /// hardware slot is provisioned at runtime. Thread-safe. + /// + /// @param config Fully-populated slot configuration. + /// @return A lightweight handle to the registered slot. + SlotHandle RegisterSlot(KeySlotConfig config); + + // Implementation note: this manager exposes slot availability only via + // `SlotAvailability` (`kActive`, `kDisabled`, `kUnavailable`). + // `kUnavailable` indicates the slot cannot be used (for example, the + // provider or hardware is unreachable); it blocks new usages while + // allowing existing in-flight usages to complete. + // + // Persistent configuration changes (adding/removing slots) are performed + // through the configuration/catalog management path and are the source of + // truth across restarts. If operators require an in-memory admin API for + // hot-reload or controlled removals in the future, introduce a dedicated + // admin endpoint that: emits audit events, documents orphaning semantics, + // and coordinates persistence with the configuration service. + + /// @brief Resolve slot name + client_id → SlotHandle. + /// + /// This is a **passive** operation — it grants the connection a lightweight + /// reference to the slot but does NOT constitute active key usage. + /// No counter increment, no side effects. ~KeySlotDataNode is a no-op. + /// + /// Resolution steps: + /// 1. Look up slot_name in m_name_index. + /// 2. Extract UID from client_id. + /// 3. Check access_policy.allowed_uids contains UID. + /// 4. Return SlotHandle on success. + /// + /// @param slot_name Human-readable slot name (e.g., "test/hmac-sha256") + /// @param client_id Composite PID|UID of the requesting connection + /// @return SlotHandle on success, or DaemonErrorCode on failure. + score::crypto::Expected ResolveSlot( + const std::string& slot_name, + data_manager::ClientId client_id) const; + + /// @brief Atomically check slot availability and acquire usage. + /// + /// @deprecated Will be removed. Usage counting is not wired into production. + + /// @brief Read-only access to config via handle. + /// + /// Returns a pointer to the central entry config. + /// + /// @param handle Slot handle obtained from RegisterSlot or ResolveSlot. + /// @return config pointer (valid for duration of operation), or kInvalidResourceId. + score::crypto::Expected GetConfig( + SlotHandle handle) const; + + /// @brief Register an application-local resource ID mapping. + /// + /// Called at startup by the catalog for each per-app mapping entry. + /// Maps (uid, app_resource_id) → actual slot name in the registry. + /// + /// @param uid UID of the application. + /// @param app_resource_id Application-local name (e.g., "signing_key"). + /// @param slot_name Actual slot name registered in this manager. + void RegisterAppResource(uint32_t uid, const std::string& app_resource_id, const std::string& slot_name); + + /// @brief Resolve an application resource ID to a SlotHandle. + /// + /// Looks up (uid, app_resource_id) in the per-UID resource map, then + /// resolves the resulting slot name with access-policy checks. + /// + /// @param app_resource_id Application-local resource name. + /// @param client_id Composite PID|UID of the requesting connection. + /// @return SlotHandle on success, or kInvalidResourceId if no mapping exists. + score::crypto::Expected ResolveAppResource( + const std::string& app_resource_id, + data_manager::ClientId client_id) const; + + /// @brief Get the total number of registered slots. Useful for testing. + std::size_t GetSlotCount() const noexcept; + + /// @brief Return the primary provider ID for a slot. + /// + /// The primary provider is `config.provider_ids[0]` — the sole writer. + /// + /// @return The primary ProviderId, or DaemonErrorCode on failure (invalid + /// handle, empty provider list). + score::crypto::Expected GetPrimaryProviderId( + SlotHandle handle) const; + + /// @brief Check whether `provider_id` is permitted to access a slot. + /// + /// Returns std::monostate if `provider_id` appears anywhere in `config.provider_ids`. + /// Returns kAccessDenied if it is not listed or the handle is invalid. + /// + /// @param handle Slot to check. + /// @param provider_id Provider to validate. + /// @return std::monostate if access is permitted, or DaemonErrorCode on failure. + score::crypto::Expected IsProviderAllowedForSlot( + SlotHandle handle, + const common::ProviderId& provider_id) const; + + /// @brief Resolve provider names to numeric IDs in all slots. + /// + /// Called once after ProviderManager::Initialize() to convert human-readable + /// provider names (from config) to numeric IDs (assigned by ProviderManager). + /// This ensures all runtime lookups use fast numeric comparisons. + /// + /// For each slot: + /// 1. Iterate over provider_names + /// 2. Call provider_manager.GetProvider(name) to get the instance + /// 3. Call instance->GetProviderId() to get the numeric ID + /// 4. Append numeric ID to provider_ids + /// 5. Log warning if a name is not found + /// + /// After this call, provider_ids is populated and ready for runtime. + /// provider_names may be cleared after this to save memory (future). + /// + /// @param provider_manager The ProviderManager instance (post-Initialize). + void ResolveProviderIds(const provider::ProviderManager& provider_manager); + + private: + /// @brief Validate a SlotHandle before use. + bool IsValidHandle(SlotHandle handle) const noexcept; + + std::vector m_registry; ///< Central slot storage + std::unordered_map m_name_index; ///< name → registry index + /// Per-UID app resource map: uid → { app_resource_id → slot_name }. + /// Populated at startup by RegisterAppResource(); read-only after that. + std::unordered_map> m_app_resource_map; + mutable std::mutex m_mutex; ///< Protects state changes + + static constexpr std::string_view LOG_PREFIX = "[SLOT_REGISTRY] "; +}; + +} // namespace score::crypto::daemon::key_management + +#endif // SCORE_CRYPTO_DAEMON_KEY_MANAGEMENT_SLOT_REGISTRY_HPP diff --git a/score/crypto/daemon/mediator/BUILD b/score/crypto/daemon/mediator/BUILD new file mode 100644 index 0000000..2f9a3c3 --- /dev/null +++ b/score/crypto/daemon/mediator/BUILD @@ -0,0 +1,47 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "mediator_operations", + hdrs = ["mediator_operations.hpp"], + visibility = [ + "//:__subpackages__", + ], + deps = ["//score/crypto/daemon/common"], +) + +cc_library( + name = "mediator", + srcs = [ + "src/mediator_impl.cpp", + ], + hdrs = [ + "i_mediator.hpp", + "src/mediator_impl.hpp", + ], + includes = ["."], + visibility = ["//:__subpackages__"], + deps = [ + ":mediator_operations", + "//score/crypto/daemon/common", + "//score/crypto/daemon/common:operation_names", + "//score/crypto/daemon/config", + "//score/crypto/daemon/control_plane:request_handler_hdr", + "//score/crypto/daemon/data_manager", + "//score/crypto/daemon/key_management", + "//score/crypto/daemon/key_management:key_management_headers", + "//score/crypto/daemon/provider:provider_headers", + "//score/crypto/daemon/provider:provider_manager", + ], +) diff --git a/score/crypto/daemon/mediator/i_mediator.hpp b/score/crypto/daemon/mediator/i_mediator.hpp new file mode 100644 index 0000000..a5e2646 --- /dev/null +++ b/score/crypto/daemon/mediator/i_mediator.hpp @@ -0,0 +1,84 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_MEDIATOR_IMediator_HPP_ +#define SCORE_CRYPTO_DAEMON_MEDIATOR_IMediator_HPP_ + +#include + +#include "score/crypto/daemon/config/inc/config.hpp" +#include "score/crypto/daemon/control_plane/i_request_handler.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" +#include "score/crypto/daemon/key_management/core/key_management_service.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" + +namespace score::crypto::daemon::mediator +{ + +/** + * @interface IMediator + * @brief Interface for mediating API requests in the crypto daemon. + * + * The IMediator interface, implementation shall classify the type of the + * crypto operation the incoming request pertains to and delegate it to the + * appropriate handler within the crypto daemon. It inherits from IRequestHandler + * to participate in the chain of responsibility pattern. + */ +class IMediator : public score::crypto::daemon::control_plane::IRequestHandler +{ + public: + /** + * @brief Default constructor. + */ + IMediator(daemon::data_manager::IDataManager::Sptr data_manager, + daemon::provider::ProviderManager::Sptr provider_manager, + const config::Config& config, + daemon::key_management::KeyManagementService::Sptr km_service = nullptr) + : m_data_manager(data_manager), + m_provider_manager(provider_manager), + m_config(config), + m_km_service(std::move(km_service)) + { + } + + /** + * @brief Virtual destructor. + */ + virtual ~IMediator() = default; + + // Delete copy and move operations + IMediator(const IMediator&) = delete; + IMediator& operator=(const IMediator&) = delete; + IMediator(IMediator&&) = delete; + IMediator& operator=(IMediator&&) = delete; + + /** + * @brief Processes a control request and generates a corresponding response. + * + * @param request The control request to be processed, containing command and + * parameters. + * @return ControlResponse The response generated after processing the + * request. + */ + virtual score::crypto::daemon::control_plane::ControlResponse processRequest( + const score::crypto::daemon::control_plane::ControlRequest& request) override = 0; + + protected: + daemon::data_manager::IDataManager::Sptr m_data_manager; + daemon::provider::ProviderManager::Sptr m_provider_manager; + const config::Config& m_config; + daemon::key_management::KeyManagementService::Sptr m_km_service; +}; + +} // namespace score::crypto::daemon::mediator + +#endif // IPC_IMediator_HPP_ diff --git a/score/crypto/daemon/mediator/mediator_operations.hpp b/score/crypto/daemon/mediator/mediator_operations.hpp new file mode 100644 index 0000000..2f307c8 --- /dev/null +++ b/score/crypto/daemon/mediator/mediator_operations.hpp @@ -0,0 +1,84 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_MEDIATOR_MEDIATOR_OPERATIONS_HPP_ +#define SCORE_CRYPTO_DAEMON_MEDIATOR_MEDIATOR_OPERATIONS_HPP_ + +#include + +#include "score/crypto/daemon/common/actors.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" + +namespace score::crypto::daemon::mediator::operations +{ + +using OperationAction = common::OperationAction; + +// ============================================================================ +// Common Mediator Operations +// ============================================================================ + +// CTX_CREATE +// Request: data_node_id = connection_id (parent node), +// param[0]: string — handler type (e.g. "HASH") +// param[1]: string — algorithm name (e.g. "SHA256", "SHA512") +// param[2]: optional uint8 — provider type preference (defaults to DEFAULT) +// param[3]: optional uint64_t — node_id of key resource (CryptoResourceId.id) +// Response: status_code (SUCCESS/error) +// uint64_t — daemon-assigned context_id (DataNodeId) +// Effect: Creates cryptographic context, initializes handler with specified algorithm +inline constexpr OperationAction CTX_CREATE = 1; + +// CTX_CLOSE +// Request: data_node_id = context_id (the context to close), +// no operation parameters +// Response: status_code (SUCCESS) +// no output parameters +// Effect: Deletes the context node from DataManager +inline constexpr OperationAction CTX_CLOSE = 2; + +// RESOURCE_RESOLVE +// Request: data_node_id = connection_id, +// param[0]: string — application-defined resource name (e.g., "HMAC_SHA256_IntegrationTestKey") +// param[1]: uint8 — ResourceType enum value (e.g., 1 = kKeySlot) +// Response: status_code (SUCCESS/error) +// param[0]: uint64 — daemon-assigned resource id +// param[1]: uint8 — ResourceType enum value +// param[2]: bool — True if resource is persistent, false if ephemeral +// param[3]: uint16 — primary_provider id +// Effect: Resolves a named resource to a daemon-scoped CryptoResourceId. +// Access control (uid-based) is enforced during resolution. +inline constexpr OperationAction RESOLVE_RESOURCE = 3; + +inline control_plane::protocol::OperationIdentifier CreateContext() +{ + return control_plane::protocol::OperationIdentifier{.operationActor = common::actors::OP_ACTOR_MEDIATOR, + .operationAction = operations::CTX_CREATE}; +} +inline control_plane::protocol::OperationIdentifier CloseContext() +{ + return control_plane::protocol::OperationIdentifier{.operationActor = common::actors::OP_ACTOR_MEDIATOR, + .operationAction = operations::CTX_CLOSE}; +} +inline control_plane::protocol::OperationIdentifier ResolveResource() +{ + return control_plane::protocol::OperationIdentifier{.operationActor = common::actors::OP_ACTOR_MEDIATOR, + .operationAction = operations::RESOLVE_RESOURCE}; +} + +// Starting point for custom OPs +inline constexpr OperationAction CUSTOM_OP_START = 1 << (std::numeric_limits::digits - 1); + +} // namespace score::crypto::daemon::mediator::operations + +#endif // SCORE_CRYPTO_DAEMON_MEDIATOR_MEDIATOR_OPERATIONS_HPP_ diff --git a/score/crypto/daemon/mediator/src/mediator_impl.cpp b/score/crypto/daemon/mediator/src/mediator_impl.cpp new file mode 100644 index 0000000..17607a8 --- /dev/null +++ b/score/crypto/daemon/mediator/src/mediator_impl.cpp @@ -0,0 +1,582 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include +#include +#include +#include +#include +#include +#include + +#include "score/crypto/daemon/common/actors.hpp" +#include "score/crypto/daemon/common/operation_names.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/config/inc/config.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/daemon/control_plane/i_request_handler.hpp" +#include "score/crypto/daemon/data_manager/data_node_accessor.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_handler.hpp" +#include "score/crypto/daemon/mediator/i_mediator.hpp" +#include "score/crypto/daemon/mediator/mediator_operations.hpp" +#include "score/crypto/daemon/mediator/src/mediator_impl.hpp" +#include "score/crypto/daemon/provider/handler/context_data_node.hpp" +#include "score/crypto/daemon/provider/handler/i_crypto_handler_factory.hpp" +#include "score/crypto/daemon/provider/handler/i_handler.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" +#include "score/mw/crypto/api/common/error_domain.hpp" + +namespace control_plane = score::crypto::daemon::control_plane; + +using ControlRequest = control_plane::ControlRequest; +using ControlResponse = control_plane::ControlResponse; + +namespace score::crypto::daemon::mediator +{ + +/// @brief Decode a ProviderType wire value (from the IPC protocol) into the +/// daemon-internal CryptoProviderType capability classification. +/// +/// The wire encoding is the uint8_t value of the client-side mw::crypto::ProviderType +/// enumerator (0=kDefault, 1=kHardware, 2=kSoftware, 3=kHardwarePreferred, 4=kSoftwarePreferred). +/// kHardwarePreferred / kSoftwarePreferred are resolved to their primary type; the +/// daemon's ProviderManager::GetProvider() handles fallback to SOFTWARE/HARDWARE if +/// the preferred type is not registered. +static common::CryptoProviderType FromWireProviderType(std::uint8_t wire_value) noexcept +{ + // Wire values match mw::crypto::ProviderType enumerator positions: + // 0=kDefault, 1=kHardware, 2=kSoftware, 3=kHardwarePreferred, 4=kSoftwarePreferred + switch (wire_value) + { + case 1: + return common::CryptoProviderType::HARDWARE; + case 2: + return common::CryptoProviderType::SOFTWARE; + case 3: + return common::CryptoProviderType::HARDWARE; + case 4: + return common::CryptoProviderType::SOFTWARE; + default: + return common::CryptoProviderType::DEFAULT; + } +} + +MediatorImpl::MediatorImpl(data_manager::IDataManager::Sptr data_manager, + provider::ProviderManager::Sptr provider_manager, + const config::Config& config, + key_management::KeyManagementService::Sptr km_service) + : IMediator(std::move(data_manager), std::move(provider_manager), config, std::move(km_service)) +{ + if (m_km_service) + { + RegisterResourceResolvers(); + } +} + +control_plane::ControlResponse MediatorImpl::processRequest(const control_plane::ControlRequest& request) +{ + std::cout << "[SCORE_API_MED] Processing request - " + << "RequestId: " << request.request_id << ", Client Id: " << request.client_id + << ", DataNodeId: " << request.data_node_id << "\n"; + auto responseBuilder = control_plane::protocol::OperationResponseBuilder(); + + for (size_t idx = 0; idx < request.operation.operations.size(); ++idx) + { + const auto& operation = request.operation.operations[idx]; + + // Stop processing requests, once one fails. They may depend on each other + if (!HandleSingleOperation(request, operation, responseBuilder)) + { + std::cout << "[SCORE_API_MED] ERROR at operation index: " << idx << "\n"; + break; + } + } + + // Build response + auto opResponse = responseBuilder.build().value_or(control_plane::protocol::OperationResponse()); + + ControlResponse response; + response.request_id = request.request_id; + response.operation = opResponse; + + std::cout << "[SCORE_API_MED] Response operations: " << opResponse.operations.size() << "\n"; + + return response; +} + +// ============================================================================ +// Helper Method Implementations +// ============================================================================ + +bool MediatorImpl::HandleSingleOperation(const control_plane::ControlRequest& request, + const control_plane::SingleOperationRequest& operation, + control_plane::protocol::OperationResponseBuilder& responseBuilder) +{ + if (operation.operationId.operationActor == common::actors::OP_ACTOR_MEDIATOR) + { + auto success = HandleMediatorOperation(request, operation, responseBuilder); + if (!success) + { + std::cout << "[SCORE_API_MED] ERROR - Failed to handle mediator operation: " + << common::OpId{operation.operationId} << "\n"; + } + return success; + } + + auto success = ForwardSingleOperation(request, operation, responseBuilder); + if (!success) + { + std::cout << "[SCORE_API_MED] ERROR - Failed to forward operation: " << common::OpId{operation.operationId} + << "\n"; + } + return success; +} + +bool MediatorImpl::HandleMediatorOperation(const control_plane::ControlRequest& request, + const control_plane::SingleOperationRequest& operation, + control_plane::protocol::OperationResponseBuilder& responseBuilder) +{ + auto operationIdentifier = operation.operationId; + + if (operationIdentifier.operationAction == operations::CTX_CREATE) + { + auto success = HandleContextCreationOperation(request, operation, responseBuilder); + if (!success) + { + std::cout << "[SCORE_API_MED] ERROR - Failed to handle context creation operation\n"; + } + + return success; + } + else if (operationIdentifier.operationAction == operations::CTX_CLOSE) + { + auto success = HandleContextCloseOperation(request, operation, responseBuilder); + if (!success) + { + std::cout << "[SCORE_API_MED] ERROR - Failed to handle context close operation\n"; + } + + return success; + } + else if (operationIdentifier.operationAction == operations::RESOLVE_RESOURCE) + { + auto success = + HandleResourceResolutionOperation(request.client_id, request.data_node_id, operation, responseBuilder); + if (!success) + { + std::cout << "[SCORE_API_MED] ERROR - Failed to handle resource resolution operation\n"; + } + return success; + } + + std::cout << "[SCORE_API_MED] ERROR - Unknown mediator operation: " << common::OpId{operationIdentifier} << "\n"; + + return false; +} + +bool MediatorImpl::ForwardSingleOperation(const control_plane::ControlRequest& request, + const control_plane::SingleOperationRequest& operation, + control_plane::protocol::OperationResponseBuilder& responseBuilder) +{ + auto operationIdentifier = operation.operationId; + auto client_id = request.client_id; + auto context_id = request.data_node_id; + + // Try to retrieve context from data manager + auto node_accessor_res = m_data_manager->getNodeAccessor(client_id, context_id); + if (!node_accessor_res.has_value()) + { + std::cout << "[SCORE_API_MED] ERROR - No context found for context_id: " << context_id << "\n"; + responseBuilder.operation(operationIdentifier) + .return_error(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + return false; + } + + auto context_node_accessor_res = + std::move(node_accessor_res).value().downCast(); + if (!context_node_accessor_res.has_value()) + { + std::cout << "[SCORE_API_MED] ERROR - Context node for context_id: " << context_id + << " is not a ContextDataNode" + << "\n"; + responseBuilder.operation(operationIdentifier) + .return_error(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + return false; + } + auto context_node_accessor = std::move(context_node_accessor_res).value(); + + auto handler = context_node_accessor->GetHandler(); + if (!handler) + { + std::cout << "[SCORE_API_MED] ERROR - Context node accessor does not contain a handler for context_id: " + << context_id << "\n"; + responseBuilder.operation(operationIdentifier).return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return false; + } + + std::cout << "[SCORE_API_MED] Context found in data manager for context_id: " << context_id << "\n"; + + // TODO: Once requests are non-const, we can drop the copy here. + auto mutable_params = operation.parameters; + + // Build execution context + const OperationExecutionContext exec_ctx{ + .operationId = operation.operationId, .context_id = context_id, .parameters = mutable_params}; + + // Call handler with handler copy, not under lock + return ExecuteOperation(exec_ctx, handler, responseBuilder); +} + +bool MediatorImpl::HandleContextCreationOperation(const score::crypto::daemon::control_plane::ControlRequest& request, + const control_plane::SingleOperationRequest& operation, + control_plane::protocol::OperationResponseBuilder& responseBuilder) +{ + std::cout << "[SCORE_API_MED] Creating context node\n"; + + if (operation.parameters.size() < 2) + { + std::cout << "[SCORE_API_MED] ERROR - Not enough parameters for request\n"; + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return false; + } + auto context_type_res = operation.getParameter(0); + if (!context_type_res.has_value()) + { + std::cout << "[SCORE_API_MED] ERROR - Wrong parameter type for context_type\n"; + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return false; + } + auto context_type = context_type_res.value(); + + auto algorithm_res = operation.getParameter(1); + if (!algorithm_res.has_value()) + { + std::cout << "[SCORE_API_MED] ERROR - Wrong parameter type for algorithm\n"; + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return false; + } + auto algorithm = algorithm_res.value(); + + // Read optional provider type parameter (param[2]) + // Default to DEFAULT provider type if not specified or invalid + common::CryptoProviderType requested_provider_type = common::CryptoProviderType::DEFAULT; + if (operation.parameters.size() >= 3) + { + auto provider_type_res = operation.getParameter(2); + if (provider_type_res.has_value()) + { + requested_provider_type = FromWireProviderType(provider_type_res.value()); + std::cout << "[SCORE_API_MED] Requested ProviderType: wire=" << static_cast(provider_type_res.value()) + << " resolved_daemon_type=" << static_cast(requested_provider_type) << "\n"; + } + } + + // Read optional key_node_id parameter (param[3]) — for binding a key at context creation + std::uint64_t key_node_id{0U}; + bool has_key_binding = false; + if (operation.parameters.size() >= 4) + { + auto key_node_res = operation.getParameter(3); + if (key_node_res.has_value()) + { + key_node_id = key_node_res.value(); + has_key_binding = true; + } + } + + // --- Resolve target provider (considers key/slot affinity when available) --- + std::shared_ptr provider; + if (m_km_service && has_key_binding) + { + auto resolved_id_res = m_km_service->ResolveTargetProvider( + request.client_id, requested_provider_type, std::optional{key_node_id}); + if (!resolved_id_res.has_value()) + { + std::cerr << "[SCORE_API_MED] ERROR - Provider resolution failed for keyed context" + << " (key_node_id=" << key_node_id << ")\n"; + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + return false; + } + std::cout << "[SCORE_API_MED] CTX_CREATE [" << context_type << "/" << algorithm + << "] resolved provider via key affinity (key_node_id=" << key_node_id + << "): provider_id=" << resolved_id_res.value() << "\n"; + provider = m_provider_manager->GetProvider(resolved_id_res.value()); + } + else + { + provider = m_provider_manager->GetProvider(requested_provider_type); + } + if (!provider) + { + std::cout << "[SCORE_API_MED] ERROR - No providers available for type: " + << static_cast(requested_provider_type) << "\n"; + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return false; + } + std::cout << "[SCORE_API_MED] CTX_CREATE [" << context_type << "/" << algorithm << "] selected provider: name='" + << provider->GetProviderName() << "' id=" << provider->GetProviderId() + << (has_key_binding ? " (key-affinity resolved)" : " (type-based selection)") << "\n"; + + auto crypto_ops = provider->GetCryptoHandlerFactory(); + if (crypto_ops == nullptr) + { + std::cout << "[SCORE_API_MED] ERROR - Crypto operations not available\n"; + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kUnsupportedOperation); + return false; + } + + auto create_result = crypto_ops->CreateHandler(std::string(context_type), std::string(algorithm)); + if (!create_result.has_value()) + { + std::cout << "[SCORE_API_MED] ERROR - Handler or algorithm not supported: " << context_type << "/" << algorithm + << "\n"; + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return false; + } + + auto handler = create_result.value(); + std::cout << "[SCORE_API_MED] Created handler pointer: " << handler.get() << "\n"; + + // --- Create the context node FIRST so we have its node-id for InitializationParams --- + auto client_id = request.client_id; + auto connection_id = request.data_node_id; + auto context_node = std::make_shared(handler, std::string(algorithm)); + + auto context_id_res = m_data_manager->addChildNode(client_id, connection_id, context_node); + if (!context_id_res.has_value()) + { + std::cerr << "[SCORE_API_MED] Adding Context to DataNodeManager failed\n"; + responseBuilder.operation(operation.operationId).return_error(context_id_res.error()); + return false; + } + auto context_node_id = context_id_res.value(); + + // --- Optional key binding: resolve key and bind to context node --- + key_management::IKeyHandler::Sptr bound_key_handler; + if (has_key_binding) + { + if (!m_km_service) + { + std::cerr << "[SCORE_API_MED] ERROR - key binding requires key management service\n"; + m_data_manager->deleteNode(client_id, context_node_id); + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kUnsupportedOperation); + return false; + } + + auto bind_res = + m_km_service->BindKeyToContext(client_id, context_node_id, key_node_id, provider->GetProviderId()); + if (!bind_res.has_value()) + { + std::cerr << "[SCORE_API_MED] ERROR - key binding failed for key_node_id=" << key_node_id << "\n"; + m_data_manager->deleteNode(client_id, context_node_id); + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + return false; + } + + key_node_id = static_cast(bind_res.value().resolved_node_id); + bound_key_handler = bind_res.value().key_handler; + } + + // --- Build InitializationParams and initialize the handler --- + provider::handler::InitializationParams init_params{}; + init_params.client_id = client_id; + init_params.context_node_id = context_node_id; + init_params.provider_id = provider->GetProviderId(); + init_params.key_node_id = key_node_id; + init_params.bound_key_handler = bound_key_handler.get(); + + // Pass raw CTX_CREATE parameters so handlers can extract extended fields + // (e.g. MAC handlers read operation_mode from param[4]). + init_params.context_creation_params = operation.parameters; + + auto init_result = handler->InitializeContext(init_params); + if (!init_result.has_value()) + { + std::cout << "[SCORE_API_MED] ERROR - Handler initialization failed for context with error: " + << static_cast(init_result.error()) << "\n"; + m_data_manager->deleteNode(client_id, context_node_id); + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return false; + } + + std::cout << "[SCORE_API_MED] Context node created for context_id: " << context_node_id << "\n"; + + // Return context_id in response (no return_result for CTX_* operations) + responseBuilder.operation(operation.operationId).return_success().return_value_uint64(context_node_id); + return true; +} + +// ============================================================================ +// Shared Operation Execution Helper +// ============================================================================ +bool MediatorImpl::ExecuteOperation(const OperationExecutionContext& exec_ctx, + const std::shared_ptr& handler, + control_plane::protocol::OperationResponseBuilder& responseBuilder) +{ + // Execute operation + std::cout << "[SCORE_API_MED] Calling handler->Execute: " << common::OpId{exec_ctx.operationId} << "\n"; + auto execute_result = handler->Execute(exec_ctx.operationId, exec_ctx.parameters); + + if (!execute_result.has_value()) + { + std::cout << "[SCORE_API_MED] ERROR - Operation execution failed with error code: " + << static_cast(execute_result.error()) << "\n"; + responseBuilder.operation(exec_ctx.operationId).return_error(execute_result.error()); + return false; + } + + // Build response + std::cout << "[SCORE_API_MED] " << common::OpId{exec_ctx.operationId} << " completed successfully\n"; + // Add all output parameters to response + responseBuilder.return_crypto_operation_response( + exec_ctx.operationId, control_plane::protocol::OPERATION_RESULT_SUCCESS, std::move(execute_result.value())); + return true; +} + +// ============================================================================ +// Private Helper: Get Handler Configuration by Handler Type and Algorithm +// ============================================================================ +bool MediatorImpl::HandleContextCloseOperation( + const score::crypto::daemon::control_plane::ControlRequest& request, + const control_plane::SingleOperationRequest& operation, + score::crypto::daemon::control_plane::protocol::OperationResponseBuilder& responseBuilder) +{ + auto client_id = request.client_id; + auto context_id = request.data_node_id; + + std::cout << "[SCORE_API_MED] Closing context_id: " << context_id << "\n"; + + // Remove context node from data manager + const bool removed = m_data_manager->deleteNode(client_id, context_id).has_value(); + + if (removed) + { + std::cout << "[SCORE_API_MED] Context_id: " << context_id << " removed from data manager\n"; + responseBuilder.operation(operation.operationId).return_success(); + return true; + } + else + { + // Context not found - this is not necessarily an error + std::cout << "[SCORE_API_MED] WARNING - Context_id: " << context_id << " not found in data manager\n"; + responseBuilder.operation(operation.operationId).return_success(); + return true; + } +} + +// ============================================================================ +// Key Management Operation Handling +// ============================================================================ + +void MediatorImpl::RegisterResourceResolvers() +{ + using RT = score::mw::crypto::ResourceType; + + // --- kKeySlot ----------------------------------------------------------- + // Resolves an application resource name to a session-scoped KeySlotDataNode + // and returns its DataNodeId as an opaque handle to the client. + // The DataNodeId is enforced per-session by the DataManager and does NOT + // expose any internal SlotRegistry registry index to user space. + m_resource_resolvers[static_cast(RT::kKeySlot)] = + [this](uint64_t client_id, + uint64_t /*session_id*/, + const std::string& resource_name, + const common::OperationIdentifier& op_id, + control_plane::protocol::OperationResponseBuilder& responseBuilder) -> bool { + if (!m_km_service) + { + responseBuilder.operation(op_id).return_error(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + return false; + } + + auto node_id_result = m_km_service->ResolveKeySlot(resource_name, client_id); + if (!node_id_result.has_value()) + { + responseBuilder.operation(op_id).return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return false; + } + + // TODO: How to get the primary provider ? + // For now just return 0 + responseBuilder.operation(op_id) + .return_value_uint64(static_cast(node_id_result.value())) + .return_value_uint8(static_cast(RT::kKeySlot)) + .return_value_bool(true) // KeySlots are always persistent + .return_value_uint16(0) + .return_success(); + return true; + }; + + // Additional resource types (kProvider, kCertSlot, kTrustAnchor, …) are + // registered here as those subsystems are implemented. Each entry is + // self-contained; no existing resolvers are modified. +} + +bool MediatorImpl::HandleResourceResolutionOperation(uint64_t client_id, + uint64_t session_id, + const control_plane::SingleOperationRequest& operation, + control_plane::protocol::OperationResponseBuilder& responseBuilder) +{ + if (operation.parameters.empty()) + { + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + return false; + } + + // param[0]: resource name (String) + const auto* name_param = std::get_if(&operation.parameters[0]); + if (!name_param) + { + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + return false; + } + const std::string resource_name{*name_param}; + + // param[1]: ResourceType cast to uint64. Defaults to kKeySlot for + // backward compatibility when the client omits the type parameter. + auto resource_type = score::mw::crypto::ResourceType::kKeySlot; + if (operation.parameters.size() > 1U) + { + if (const auto* type_param = std::get_if(&operation.parameters[1])) + { + resource_type = static_cast(static_cast(*type_param)); + } + } + + const auto key = static_cast(resource_type); + const auto it = m_resource_resolvers.find(key); + if (it == m_resource_resolvers.end()) + { + std::cerr << "[SCORE_API_MED] RESOLVE_RESOURCE: no resolver registered for ResourceType=" + << static_cast(key) << "\n"; + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kUnsupportedOperation); + return false; + } + + return it->second(client_id, session_id, resource_name, operation.operationId, responseBuilder); +} + +} // namespace score::crypto::daemon::mediator diff --git a/score/crypto/daemon/mediator/src/mediator_impl.hpp b/score/crypto/daemon/mediator/src/mediator_impl.hpp new file mode 100644 index 0000000..7214de3 --- /dev/null +++ b/score/crypto/daemon/mediator/src/mediator_impl.hpp @@ -0,0 +1,116 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_MEDIATOR_IMPL_HPP_ +#define SCORE_CRYPTO_DAEMON_MEDIATOR_IMPL_HPP_ + +#include "score/crypto/daemon/mediator/i_mediator.hpp" + +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/config/inc/config.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/daemon/control_plane/i_request_handler.hpp" +#include "score/crypto/daemon/data_manager/data_node_accessor.hpp" +#include "score/crypto/daemon/data_manager/i_data_manager.hpp" +#include "score/crypto/daemon/provider/handler/context_data_node.hpp" +#include "score/crypto/daemon/provider/handler/i_handler.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::mediator +{ + +/** + * @brief Struct to hold context for executing a crypto operation, used to pass information + */ +struct OperationExecutionContext +{ + common::OperationIdentifier operationId; ///< Operation identifier + std::uint64_t context_id; ///< Context ID for logging/tracking + common::RequestParameters& parameters; ///< Parameters from the request +}; + +class MediatorImpl : public IMediator +{ + public: + MediatorImpl(data_manager::IDataManager::Sptr data_manager, + provider::ProviderManager::Sptr provider_manager, + const config::Config& config, + key_management::KeyManagementService::Sptr km_service = nullptr); + + MediatorImpl(const MediatorImpl&) = delete; + MediatorImpl& operator=(const MediatorImpl&) = delete; + + MediatorImpl(MediatorImpl&&) = default; + MediatorImpl& operator=(MediatorImpl&&) = default; + + ~MediatorImpl() override = default; + score::crypto::daemon::control_plane::ControlResponse processRequest( + const score::crypto::daemon::control_plane::ControlRequest& request) override; + + private: + bool HandleContextCreationOperation( + const score::crypto::daemon::control_plane::ControlRequest& request, + const control_plane::SingleOperationRequest& operation, + score::crypto::daemon::control_plane::protocol::OperationResponseBuilder& responseBuilder); + + bool HandleContextCloseOperation( + const score::crypto::daemon::control_plane::ControlRequest& request, + const control_plane::SingleOperationRequest& operation, + score::crypto::daemon::control_plane::protocol::OperationResponseBuilder& responseBuilder); + + // Private helpers + // Shared operation execution helper - handles parameter extraction, execution, and response building + bool ExecuteOperation(const OperationExecutionContext& exec_ctx, + const std::shared_ptr& handler, + score::crypto::daemon::control_plane::protocol::OperationResponseBuilder& responseBuilder); + + bool HandleSingleOperation(const control_plane::ControlRequest& request, + const control_plane::SingleOperationRequest& operation, + control_plane::protocol::OperationResponseBuilder& responseBuilder); + + bool HandleResourceResolutionOperation(uint64_t client_id, + uint64_t session_id, + const control_plane::SingleOperationRequest& operation, + control_plane::protocol::OperationResponseBuilder& responseBuilder); + void RegisterResourceResolvers(); + + /// Dispatch table for session-level resource resolution. + /// Key: static_cast(ResourceType). Populated once in RegisterResourceResolvers(). + /// Adding a new resource type: register one lambda here — no other code changes required. + using ResourceResolverFn = std::function; + std::unordered_map m_resource_resolvers; + + bool HandleMediatorOperation(const control_plane::ControlRequest& request, + const control_plane::SingleOperationRequest& operation, + control_plane::protocol::OperationResponseBuilder& responseBuilder); + bool ForwardSingleOperation(const control_plane::ControlRequest& request, + const control_plane::SingleOperationRequest& operation, + control_plane::protocol::OperationResponseBuilder& responseBuilder); +}; + +} // namespace score::crypto::daemon::mediator + +#endif // SCORE_CRYPTO_DAEMON_MEDIATOR_IMPL_HPP_ diff --git a/score/crypto/daemon/provider/BUILD b/score/crypto/daemon/provider/BUILD new file mode 100644 index 0000000..79f5cbb --- /dev/null +++ b/score/crypto/daemon/provider/BUILD @@ -0,0 +1,38 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +cc_library( + name = "provider_headers", + hdrs = [ + "i_provider.hpp", + "i_provider_factory.hpp", + "provider_manager.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/daemon/common", + "//score/crypto/daemon/config", + "//score/crypto/daemon/provider/handler:context_node", + "//score/crypto/daemon/provider/handler:crypto_handler_factory_headers", + ], +) + +cc_library( + name = "provider_manager", + srcs = ["src/provider_manager.cpp"], + visibility = ["//:__subpackages__"], + deps = [ + ":provider_headers", + ], +) diff --git a/score/crypto/daemon/provider/executors/BUILD b/score/crypto/daemon/provider/executors/BUILD new file mode 100644 index 0000000..345dbea --- /dev/null +++ b/score/crypto/daemon/provider/executors/BUILD @@ -0,0 +1,32 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "key_mgmt_executor", + srcs = ["src/key_mgmt_executor.cpp"], + hdrs = [ + "key_mgmt_context.hpp", + "key_mgmt_executor.hpp", + "key_mgmt_request_parser.hpp", + ], + visibility = [ + "//score/crypto/daemon/provider:__subpackages__", + "//tests:__subpackages__", + ], + deps = [ + "//score/crypto/daemon/common", + "//score/crypto/daemon/key_management", + "//score/crypto/daemon/provider/handler:handler_headers", + ], +) diff --git a/score/crypto/daemon/provider/executors/key_mgmt_context.hpp b/score/crypto/daemon/provider/executors/key_mgmt_context.hpp new file mode 100644 index 0000000..e54f634 --- /dev/null +++ b/score/crypto/daemon/provider/executors/key_mgmt_context.hpp @@ -0,0 +1,37 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_EXECUTORS_KEY_MGMT_CONTEXT_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_EXECUTORS_KEY_MGMT_CONTEXT_HPP + +#include "score/crypto/daemon/common/types.hpp" + +#include + +namespace score::crypto::daemon::provider::crypto_executor +{ + +/// Groups the stable per-context parameters for KeyManagementExecutor operations. +/// +/// These values are set once during InitializeContext() and remain constant for +/// the lifetime of the handler context. Passing them as a struct reduces the +/// executor's Execute() call from 5 parameters to 3 (context, operationId, request). +struct KeyMgmtExecutionContext +{ + common::ProviderId provider_id{common::kInvalidProviderId}; + std::uint64_t client_id{0U}; + std::uint64_t context_node_id{0U}; +}; + +} // namespace score::crypto::daemon::provider::crypto_executor + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_EXECUTORS_KEY_MGMT_CONTEXT_HPP diff --git a/score/crypto/daemon/provider/executors/key_mgmt_executor.hpp b/score/crypto/daemon/provider/executors/key_mgmt_executor.hpp new file mode 100644 index 0000000..438cd62 --- /dev/null +++ b/score/crypto/daemon/provider/executors/key_mgmt_executor.hpp @@ -0,0 +1,82 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_EXECUTORS_KEY_MGMT_EXECUTOR_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_EXECUTORS_KEY_MGMT_EXECUTOR_HPP + +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/key_management/core/key_management_service.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_factory.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp" +#include "score/crypto/daemon/provider/executors/key_mgmt_context.hpp" + +#include +#include + +namespace score::crypto::daemon::provider::crypto_executor +{ + +/// Stateless executor for key management operations. +/// +/// Each provider-side handler (OpenSslKeyManagementHandler, Pkcs11KeyManagementHandler) +/// receives an instance from the factory and delegates Execute() to it. The stable +/// collaborators (factory, slot_handler, service) are injected at construction and +/// never change; the per-context identifiers (provider_id, client_id, context_node_id) +/// are passed as a KeyMgmtExecutionContext struct. +class KeyManagementExecutor final +{ + public: + KeyManagementExecutor(std::shared_ptr factory, + std::shared_ptr slot_handler, + std::shared_ptr service); + ~KeyManagementExecutor() = default; + + KeyManagementExecutor(const KeyManagementExecutor&) = delete; + KeyManagementExecutor& operator=(const KeyManagementExecutor&) = delete; + KeyManagementExecutor(KeyManagementExecutor&&) = delete; + KeyManagementExecutor& operator=(KeyManagementExecutor&&) = delete; + + /// Dispatch a key management operation. + /// + /// @param ctx Stable per-context parameters (provider_id, client_id, context_node_id). + /// @param operationId Operation and action identifiers. + /// @param request Packed request parameters from the client. + [[nodiscard]] Expected Execute( + const KeyMgmtExecutionContext& ctx, + const common::OperationIdentifier& operationId, + common::RequestParameters& request); + + private: + std::shared_ptr m_factory; + std::shared_ptr m_slot_handler; + std::shared_ptr m_service; + + [[nodiscard]] Expected HandleGenerate( + const KeyMgmtExecutionContext& ctx, + common::RequestParameters& request); + + [[nodiscard]] Expected HandleLoad( + const KeyMgmtExecutionContext& ctx, + common::RequestParameters& request); + + [[nodiscard]] Expected HandleRelease( + std::uint64_t client_id, + common::RequestParameters& request); + + [[nodiscard]] Expected HandleSlotInfo( + std::uint64_t client_id, + common::RequestParameters& request); +}; + +} // namespace score::crypto::daemon::provider::crypto_executor + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_EXECUTORS_KEY_MGMT_EXECUTOR_HPP diff --git a/score/crypto/daemon/provider/executors/key_mgmt_request_parser.hpp b/score/crypto/daemon/provider/executors/key_mgmt_request_parser.hpp new file mode 100644 index 0000000..de0d35f --- /dev/null +++ b/score/crypto/daemon/provider/executors/key_mgmt_request_parser.hpp @@ -0,0 +1,119 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_EXECUTORS_KEY_MGMT_REQUEST_PARSER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_EXECUTORS_KEY_MGMT_REQUEST_PARSER_HPP + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_types.hpp" + +#include +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::crypto_executor +{ +namespace key_mgmt_request_parser +{ + +/// Extract a uint64 parameter at the given index. +/// +/// @return The extracted value, or ERROR_INSUFFICIENT_PARAMETERS / ERROR_INVALID_PARAMETER. +[[nodiscard]] inline Expected ExtractUint64( + const common::RequestParameters& request, + std::size_t index) +{ + if (request.size() <= index) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientParameters); + } + const auto* val = std::get_if(&request[index]); + if (val == nullptr) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + return *val; +} + +/// Extract a non-empty string_view parameter at the given index. +/// +/// @return The extracted view, or ERROR_INSUFFICIENT_PARAMETERS / ERROR_INVALID_PARAMETER. +[[nodiscard]] inline Expected ExtractAlgorithm( + const common::RequestParameters& request, + std::size_t index) +{ + if (request.size() <= index) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientParameters); + } + const auto* val = std::get_if(&request[index]); + if ((val == nullptr) || val->empty()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + return *val; +} + +/// Try to extract an optional uint64 permission value at the given index. +/// +/// Returns std::nullopt when the index is out of range or the variant +/// alternative does not hold a uint64_t (both are silently acceptable). +[[nodiscard]] inline std::optional ExtractOptionalPermissions(const common::RequestParameters& request, + std::size_t index) +{ + if (request.size() <= index) + { + return std::nullopt; + } + const auto* val = std::get_if(&request[index]); + if (val == nullptr) + { + return std::nullopt; + } + + return *val; +} + +/// Build a KeyGenerationRequest from the packed request parameters. +/// +/// Expected layout: +/// request[0] = algorithm (string_view, required) +/// request[1] = permissions (uint64_t, optional) +[[nodiscard]] inline Expected +BuildGenerationRequest(const common::RequestParameters& request) +{ + auto algo = ExtractAlgorithm(request, 0U); + if (!algo.has_value()) + { + return score::crypto::make_unexpected(algo.error()); + } + + key_management::KeyGenerationRequest req{}; + req.algorithm = std::string(algo.value()); + + const auto perm = ExtractOptionalPermissions(request, 1U); + if (perm.has_value()) + { + req.permissions = static_cast(perm.value()); + } + + return req; +} + +} // namespace key_mgmt_request_parser +} // namespace score::crypto::daemon::provider::crypto_executor + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_EXECUTORS_KEY_MGMT_REQUEST_PARSER_HPP diff --git a/score/crypto/daemon/provider/executors/src/key_mgmt_executor.cpp b/score/crypto/daemon/provider/executors/src/key_mgmt_executor.cpp new file mode 100644 index 0000000..b7cafb4 --- /dev/null +++ b/score/crypto/daemon/provider/executors/src/key_mgmt_executor.cpp @@ -0,0 +1,251 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/executors/key_mgmt_executor.hpp" + +#include "score/crypto/daemon/key_management/interfaces/key_management_operations.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_types.hpp" +#include "score/crypto/daemon/provider/executors/key_mgmt_request_parser.hpp" + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include +#include + +namespace score::crypto::daemon::provider::crypto_executor +{ +namespace +{ +constexpr std::string_view LOG_PREFIX = "[KEY_MGMT_EXEC] "; + +namespace km_ops = key_management::operations; +namespace km_parse = key_mgmt_request_parser; +} // namespace + +// --------------------------------------------------------------------------- +// Constructor +// --------------------------------------------------------------------------- + +KeyManagementExecutor::KeyManagementExecutor(std::shared_ptr factory, + std::shared_ptr slot_handler, + std::shared_ptr service) + : m_factory{std::move(factory)}, m_slot_handler{std::move(slot_handler)}, m_service{std::move(service)} +{ +} + +// --------------------------------------------------------------------------- +// Execute — top-level dispatch +// --------------------------------------------------------------------------- + +Expected KeyManagementExecutor::Execute( + const KeyMgmtExecutionContext& ctx, + const common::OperationIdentifier& operationId, + common::RequestParameters& request) +{ + const auto action = operationId.operationAction; + + if (action == km_ops::KEY_GENERATE) + { + return HandleGenerate(ctx, request); + } + if (action == km_ops::KEY_LOAD) + { + return HandleLoad(ctx, request); + } + if (action == km_ops::KEY_RELEASE) + { + return HandleRelease(ctx.client_id, request); + } + if (action == km_ops::KEY_SLOT_INFO) + { + return HandleSlotInfo(ctx.client_id, request); + } + + // PKCS#11-specific operations — stubs for future implementation. + if ((action == km_ops::KEY_WRAP) || (action == km_ops::KEY_UNWRAP) || (action == km_ops::KEY_DERIVE)) + { + std::cerr << LOG_PREFIX << "Execute: operation not yet implemented (0x" << std::hex + << static_cast(action) << std::dec << ")\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); + } + + std::cerr << LOG_PREFIX << "Execute: unsupported action 0x" << std::hex << static_cast(action) << std::dec + << '\n'; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); +} + +// --------------------------------------------------------------------------- +// HandleGenerate +// +// Parameter layout: +// client_id – authenticated client (from InitializationParams) +// context_node_id – parent node for new key node +// request[0] = algorithm (string_view) +// request[1] = permissions (uint64_t, optional) +// --------------------------------------------------------------------------- + +Expected +KeyManagementExecutor::HandleGenerate(const KeyMgmtExecutionContext& ctx, common::RequestParameters& request) +{ + auto gen_req = km_parse::BuildGenerationRequest(request); + if (!gen_req.has_value()) + { + std::cerr << LOG_PREFIX << "HandleGenerate: invalid request parameters\n"; + return score::crypto::make_unexpected(gen_req.error()); + } + + // Provider-specific: generate the key. The returned IKeyHandler owns the material. + auto gen_result = m_factory->GenerateKey(gen_req.value()); + if (!gen_result.has_value()) + { + std::cerr << LOG_PREFIX << "HandleGenerate: GenerateKey failed\n"; + return score::crypto::make_unexpected( + score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + + // Orchestration: transfer handler ownership to the DataManager. + // On failure the IKeyHandler destructor cleans up key material automatically. + auto node_id_result = m_service->RegisterKeyMaterial({ctx.client_id, ctx.context_node_id, ctx.provider_id}, + std::move(gen_result.value())); + if (!node_id_result.has_value()) + { + return score::crypto::make_unexpected( + score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + + common::ResponseParameters output; + output.push_back(static_cast(node_id_result.value().node_id)); + // Return the numeric provider ID assigned by ProviderManager + output.push_back(static_cast(ctx.provider_id)); + return output; +} + +// --------------------------------------------------------------------------- +// HandleLoad +// +// Parameter layout: +// client_id – authenticated client (from InitializationParams) +// context_node_id – parent context node +// request[0] = slot_node_id (uint64_t) +// --------------------------------------------------------------------------- + +Expected KeyManagementExecutor::HandleLoad( + const KeyMgmtExecutionContext& ctx, + common::RequestParameters& request) +{ + auto slot_nid_res = km_parse::ExtractUint64(request, 0U); + if (!slot_nid_res.has_value()) + { + std::cerr << LOG_PREFIX << "HandleLoad: invalid slot node id\n"; + return score::crypto::make_unexpected(slot_nid_res.error()); + } + const auto slot_nid = slot_nid_res.value(); + + // Orchestration: resolve slot to config. + auto slot_res = m_service->ResolveSlotForOperation(ctx.client_id, slot_nid); + if (!slot_res.has_value()) + { + std::cerr << LOG_PREFIX << "HandleLoad: slot resolution failed\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + const auto& resolution = slot_res.value(); + + // Orchestration: load or reuse an existing key from the registry. + auto node_id_result = m_service->LoadOrShare( + {ctx.client_id, ctx.context_node_id, ctx.provider_id, resolution.handle}, *m_slot_handler, *resolution.config); + if (!node_id_result.has_value()) + { + std::cerr << LOG_PREFIX << "HandleLoad: LoadOrShare failed\n"; + return score::crypto::make_unexpected( + score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + + common::ResponseParameters output; + output.push_back(static_cast(node_id_result.value().node_id)); + // Return the numeric provider ID assigned by ProviderManager + output.push_back(static_cast(ctx.provider_id)); + return output; +} + +// --------------------------------------------------------------------------- +// HandleRelease +// +// Parameter layout: +// client_id – authenticated client (from InitializationParams) +// request[0] = key_node_id (uint64_t) +// --------------------------------------------------------------------------- + +Expected +KeyManagementExecutor::HandleRelease(std::uint64_t client_id, common::RequestParameters& request) +{ + auto key_nid_res = km_parse::ExtractUint64(request, 0U); + if (!key_nid_res.has_value()) + { + std::cerr << LOG_PREFIX << "HandleRelease: invalid key node id\n"; + return score::crypto::make_unexpected(key_nid_res.error()); + } + + auto release_result = m_service->ReleaseKeyMaterial(client_id, key_nid_res.value()); + if (!release_result.has_value()) + { + std::cerr << LOG_PREFIX << "HandleRelease: release failed\n"; + return score::crypto::make_unexpected( + score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + + common::ResponseParameters output; + output.push_back(static_cast(0U)); // success + return output; +} + +// --------------------------------------------------------------------------- +// HandleSlotInfo +// +// Parameter layout: +// client_id – authenticated client (from InitializationParams) +// request[0] = slot_node_id (uint64_t) +// --------------------------------------------------------------------------- + +Expected +KeyManagementExecutor::HandleSlotInfo(std::uint64_t client_id, common::RequestParameters& request) +{ + auto slot_nid_res = km_parse::ExtractUint64(request, 0U); + if (!slot_nid_res.has_value()) + { + std::cerr << LOG_PREFIX << "HandleSlotInfo: invalid slot node id\n"; + return score::crypto::make_unexpected(slot_nid_res.error()); + } + + auto slot_res = m_service->ResolveSlotForOperation(client_id, slot_nid_res.value()); + if (!slot_res.has_value()) + { + std::cerr << LOG_PREFIX << "HandleSlotInfo: slot resolution failed\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + // Provider-specific: query slot metadata. + auto info_result = m_slot_handler->GetSlotInfo(*slot_res.value().config); + if (!info_result.has_value()) + { + std::cerr << LOG_PREFIX << "HandleSlotInfo: GetSlotInfo failed\n"; + return score::crypto::make_unexpected( + score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + + const auto& info = info_result.value(); + common::ResponseParameters output; + output.push_back(static_cast(info.state)); + output.push_back(static_cast(info.state != score::mw::crypto::KeySlotState::kEmpty ? 1U : 0U)); + output.push_back(static_cast(info.permitted_operations)); + return output; +} + +} // namespace score::crypto::daemon::provider::crypto_executor diff --git a/score/crypto/daemon/provider/handler/BUILD b/score/crypto/daemon/provider/handler/BUILD new file mode 100644 index 0000000..5e77156 --- /dev/null +++ b/score/crypto/daemon/provider/handler/BUILD @@ -0,0 +1,100 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "handler_headers", + hdrs = [ + "handler_init_params.hpp", + "i_handler.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/common:common_types", + "//score/crypto/daemon/common", + "//score/crypto/daemon/key_management:key_handler_iface", + "//score/mw/crypto/api/common:crypto_common", + ], +) + +cc_library( + name = "crypto_handler_factory_headers", + hdrs = ["i_crypto_handler_factory.hpp"], + visibility = ["//visibility:public"], + deps = [ + ":handler_headers", + ":handler_utils_hdr", + "//score/crypto/daemon/common", + "@score_baselibs//score/result", + ], +) + +# Handler utilities header library +cc_library( + name = "handler_utils_hdr", + hdrs = ["src/handler_utils.hpp"], + visibility = [ + "//score/crypto/daemon/provider:__subpackages__", + "//score/crypto/daemon/src/provider:__subpackages__", + ], + deps = [ + "//score/crypto/common:common_types", + "//score/crypto/daemon/common", + ], +) + +# Handler utilities implementation library +cc_library( + name = "handler_utils_impl", + srcs = ["src/handler_utils.cpp"], + hdrs = ["src/handler_utils.hpp"], + visibility = [ + "//score/crypto/daemon/provider:__subpackages__", + "//score/crypto/daemon/src/provider:__subpackages__", + ], + deps = [ + ":handler_utils_hdr", + "//score/crypto/daemon/common", + ], +) + +# Hash handler operations header-only library +cc_library( + name = "hash_handler_operations", + hdrs = ["operations/hash_handler_operations.hpp"], + visibility = [ + "//:__subpackages__", + ], + deps = ["//score/crypto/daemon/common"], +) + +cc_library( + name = "context_node", + srcs = ["src/context_data_node.cpp"], + hdrs = ["context_data_node.hpp"], + visibility = ["//visibility:public"], + deps = [ + ":handler_headers", + "//score/crypto/daemon/common", + "//score/crypto/daemon/data_manager", + "//score/crypto/daemon/key_management:key_management_headers", + ], +) + +# MAC handler operations header-only library +cc_library( + name = "mac_handler_operations", + hdrs = ["operations/mac_handler_operations.hpp"], + visibility = ["//:__subpackages__"], + deps = ["//score/crypto/daemon/common"], +) diff --git a/score/crypto/daemon/provider/handler/context_data_node.hpp b/score/crypto/daemon/provider/handler/context_data_node.hpp new file mode 100644 index 0000000..b419c39 --- /dev/null +++ b/score/crypto/daemon/provider/handler/context_data_node.hpp @@ -0,0 +1,57 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_HANDLER_CONTEXT_DATA_NODE_HPP_ +#define SCORE_CRYPTO_DAEMON_PROVIDER_HANDLER_CONTEXT_DATA_NODE_HPP_ + +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/provider/handler/i_handler.hpp" +#include +#include + +namespace score::crypto::daemon::provider::handler +{ + +/** + * @brief DataNode specialization for storing crypto operation context information + * + * Stores handler, algorithm, and configuration for a crypto context. + * Used by MediatorImpl to persist context across multiple operations. + * + * Key binding lifecycle: when a key is bound to a context, the mediator + * creates a child KeyDataNode under this context in the data tree. + * Cascade deletion of this context automatically releases the key binding. + */ +class ContextDataNode : public data_manager::DataNode +{ + public: + ContextDataNode(std::shared_ptr handler, + const std::string& algorithm); + + ~ContextDataNode() override; + + [[nodiscard]] data_manager::DataNodeType GetNodeType() const noexcept override + { + return data_manager::DataNodeType::kContext; + } + + std::shared_ptr GetHandler() const; + const std::string& GetAlgorithm() const; + + private: + std::shared_ptr m_handler; + std::string m_algorithm; +}; + +} // namespace score::crypto::daemon::provider::handler + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_HANDLER_CONTEXT_DATA_NODE_HPP_ diff --git a/score/crypto/daemon/provider/handler/handler_init_params.hpp b/score/crypto/daemon/provider/handler/handler_init_params.hpp new file mode 100644 index 0000000..807556e --- /dev/null +++ b/score/crypto/daemon/provider/handler/handler_init_params.hpp @@ -0,0 +1,55 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_HANDLER_INIT_PARAMS_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_HANDLER_INIT_PARAMS_HPP + +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_handler.hpp" +#include "score/mw/crypto/api/common/types.hpp" +#include +#include + +namespace score::crypto::daemon::provider::handler +{ + +/// Runtime context supplied by the mediator during single-phase CTX_CREATE. +/// +/// Carries the authenticated client identity, the assigned context node ID, +/// provider identity, and an optional bound key handler for keyed contexts +/// (MAC, cipher). The bound_key_handler pointer is valid for the duration of +/// the InitializeContext() call only. +struct InitializationParams +{ + std::uint64_t client_id{0U}; ///< IPC-authenticated client identity + std::uint64_t context_node_id{0U}; ///< DataNodeId assigned by the DataManager + common::ProviderId provider_id{common::kInvalidProviderId}; ///< Numeric provider ID for this context + std::uint64_t key_node_id{0U}; ///< Client-supplied key ref node (0 = no key) + + /// Non-owning pointer to the bound key's IKeyHandler. + /// Handlers downcast to their provider-specific implementation + /// (e.g. OpenSslKeyHandler, Pkcs11KeyHandler) to access + /// key material in a type-safe manner. + /// Set by the mediator after BindKeyToContext(); nullptr when no key is bound. + const key_management::IKeyHandler* bound_key_handler{nullptr}; + + /// Raw CTX_CREATE parameters [0..N] as received on the wire. + /// Handlers may extract provider-specific extended parameters from this + /// vector without requiring mediator changes for each new field. + /// Wire layout: [0]=context_type, [1]=algorithm, [2]=provider_type(opt), + /// [3]=key_node_id(opt), [4]=operation_mode(opt, MAC only). + common::RequestParameters context_creation_params{}; +}; + +} // namespace score::crypto::daemon::provider::handler + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_HANDLER_INIT_PARAMS_HPP diff --git a/score/crypto/daemon/provider/handler/i_crypto_handler_factory.hpp b/score/crypto/daemon/provider/handler/i_crypto_handler_factory.hpp new file mode 100644 index 0000000..4fdb301 --- /dev/null +++ b/score/crypto/daemon/provider/handler/i_crypto_handler_factory.hpp @@ -0,0 +1,57 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef ICRYPTO_HANDLER_FACTORY_HPP +#define ICRYPTO_HANDLER_FACTORY_HPP +#include "i_handler.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/result/result.h" +#include +#include + +namespace score::crypto +{ +namespace daemon +{ +namespace provider +{ +namespace handler +{ + +/** + * @brief Abstract interface for cryptographic handler creation. + */ +class ICryptoHandlerFactory +{ + public: + using Sptr = std::shared_ptr; + + virtual ~ICryptoHandlerFactory() = default; + + /** + * @brief Create a handler for the given handler type and algorithm. + * + * @param handlerId Handler type (e.g. "HASH", "MAC", "KEY_MANAGEMENT"). + * @param algorithm Requested algorithm (e.g. "SHA256", "HMAC-SHA256"). + * @return The created Handler on success, or an error if the type or + * algorithm is not supported. + */ + virtual ::score::Result CreateHandler(const common::HandlerId& handlerId, + const common::AlgorithmId& algorithm) = 0; +}; + +} // namespace handler +} // namespace provider +} // namespace daemon +} // namespace score::crypto + +#endif // ICRYPTO_HANDLER_FACTORY_HPP diff --git a/score/crypto/daemon/provider/handler/i_handler.hpp b/score/crypto/daemon/provider/handler/i_handler.hpp new file mode 100644 index 0000000..add2ce2 --- /dev/null +++ b/score/crypto/daemon/provider/handler/i_handler.hpp @@ -0,0 +1,44 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_DAEMON_PROVIDER_HANDLER_HANDLER_HPP +#define CRYPTO_DAEMON_PROVIDER_HANDLER_HANDLER_HPP +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/handler/handler_init_params.hpp" +#include + +namespace score::crypto::daemon::provider::handler +{ + +class Handler +{ + public: + using Sptr = std::shared_ptr; + virtual ~Handler() = default; + + /// Initialise the handler with runtime context supplied by the mediator at + /// CTX_CREATE time. The handler's algorithm and capabilities are already + /// fixed at construction — this method wires the per-request identity and + /// optional bound key handle. + virtual Expected InitializeContext( + const handler::InitializationParams& init_params) = 0; + virtual Expected Execute( + const common::OperationIdentifier& operationId, + common::RequestParameters& request) = 0; + virtual Expected Reset() = 0; +}; + +} // namespace score::crypto::daemon::provider::handler + +#endif // CRYPTO_DAEMON_PROVIDER_HANDLER_HANDLER_HPP diff --git a/score/crypto/daemon/provider/handler/operations/hash_handler_operations.hpp b/score/crypto/daemon/provider/handler/operations/hash_handler_operations.hpp new file mode 100644 index 0000000..73ef13f --- /dev/null +++ b/score/crypto/daemon/provider/handler/operations/hash_handler_operations.hpp @@ -0,0 +1,101 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_DAEMON_PROVIDER_HANDLER_HASHHANDLER_OPERATIONS_HPP +#define CRYPTO_DAEMON_PROVIDER_HANDLER_HASHHANDLER_OPERATIONS_HPP + +#include "score/crypto/daemon/common/types.hpp" + +#include + +namespace score +{ +namespace crypto +{ +namespace daemon +{ +namespace provider +{ +namespace handler +{ +namespace hash_handler_operations +{ +using OperationAction = common::OperationAction; + +// ============================================================================ +// Common Hash Operations +// ============================================================================ +// These base operations are reserved in the lower 16-bits of OperationAction +// and are shared across all hash handler implementations. +// ============================================================================ + +// HASH_INIT +// Request: data_node_id = context_id, +// param[0]: optional DataBuffer — initial data to hash or IV +// Response: status_code (SUCCESS/error) +// no output parameters +// Effect: Calls StartHash(), initializes hash stream context, transitions state IDLE → INIT +inline constexpr OperationAction HASH_INIT = 1; + +// HASH_UPDATE +// Request: data_node_id = context_id, +// param[0]: DataBuffer — data to hash +// Response: status_code (SUCCESS/error) +// no output parameters +// Effect: Calls UpdateHash(data), processes data into stream, transitions state INIT/ACTIVE → ACTIVE +inline constexpr OperationAction HASH_UPDATE = 2; + +// HASH_FINISH +// Request: data_node_id = context_id, +// param[0]: optional DataBuffer — output buffer for hash digest +// param[1]: optional DataBuffer — final data chunk to include +// Response: status_code (SUCCESS/error) +// param[0]: DataBuffer — computed hash digest bytes +// Effect: Calls FinalizeHash(), computes final hash, clears stream context, transitions state → IDLE +inline constexpr OperationAction HASH_FINISH = 3; + +// HASH_SS (Single-Shot Hash) +// Request: data_node_id = context_id, +// param[0]: DataBuffer — data to hash +// param[1]: optional DataBuffer — output buffer for hash digest +// param[2]: optional DataBuffer — initialization vector (unused for hash) +// Response: status_code (SUCCESS/error) +// param[0]: DataBuffer — computed hash digest bytes +// Effect: Calls SingleShotHash(), requires IDLE state, performs init+update+finalize in one call +inline constexpr OperationAction HASH_SS = 4; + +// HASH_GET_DIGEST_SIZE +// Request: data_node_id = context_id, +// no operation parameters +// Response: status_code (SUCCESS/error) +// param[0]: uint64_t — hash output size in bytes (e.g., 32 for SHA256, 64 for SHA512) +// Effect: Queries hash algorithm's digest size, does not affect state +inline constexpr OperationAction HASH_GET_DIGEST_SIZE = 5; + +// HASH_RESET +// Request: data_node_id = context_id, +// no operation parameters +// Response: status_code (SUCCESS/error) +// no output parameters +// Effect: Calls Reset(), clears intermediate hash state, transitions state → IDLE +inline constexpr OperationAction HASH_RESET = 6; + +inline constexpr OperationAction HASH_CUSTOM_OP_START = 1 << (std::numeric_limits::digits - 1); + +} // namespace hash_handler_operations +} // namespace handler +} // namespace provider +} // namespace daemon +} // namespace crypto +} // namespace score + +#endif // CRYPTO_DAEMON_PROVIDER_HANDLER_HASHHANDLER_OPERATIONS_HPP diff --git a/score/crypto/daemon/provider/handler/operations/mac_handler_operations.hpp b/score/crypto/daemon/provider/handler/operations/mac_handler_operations.hpp new file mode 100644 index 0000000..45494d5 --- /dev/null +++ b/score/crypto/daemon/provider/handler/operations/mac_handler_operations.hpp @@ -0,0 +1,103 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_DAEMON_PROVIDER_CRYPTO_HANDLERS_MAC_HANDLER_OPERATIONS_HPP +#define CRYPTO_DAEMON_PROVIDER_CRYPTO_HANDLERS_MAC_HANDLER_OPERATIONS_HPP + +#include "score/crypto/daemon/common/types.hpp" + +#include + +namespace score +{ +namespace crypto +{ +namespace daemon +{ +namespace provider +{ +namespace handler +{ +namespace mac_handler_operations +{ +using OperationAction = common::OperationAction; + +// ============================================================================ +// Common MAC Operations +// ============================================================================ + +// TODO: I guess for GMAC we want an IV, but the API does currenlty not allow one + +// MAC_INIT +// Request: data_node_id = context_id, +// param[0]: optional DataBuffer — initial data or IV +// Response: status_code (SUCCESS/error) +// no output parameters +// Effect: Calls StartMac(), initializes MAC stream context, transitions state IDLE → INIT +inline constexpr OperationAction MAC_INIT = 1; + +// MAC_UPDATE +// Request: data_node_id = context_id, +// param[0]: DataBuffer — data to MAC +// Response: status_code (SUCCESS/error) +// no output parameters +// Effect: Calls UpdateMac(data), processes data into stream, transitions state INIT/ACTIVE → ACTIVE +inline constexpr OperationAction MAC_UPDATE = 2; + +// TODO: Do we need here a final chunk? + +// MAC_FINAL +// Request: data_node_id = context_id, +// param[0]: optional DataBuffer — output buffer for MAC tag (This can only be a resolved SHM region) +// param[1]: optional DataBuffer — final data chunk to include +// Response: status_code (SUCCESS/error) +// param[0]: DataBuffer — computed MAC tag bytes +// Effect: Calls FinalizeMac(), computes final MAC, clears stream context, transitions state → IDLE +inline constexpr OperationAction MAC_FINAL = 3; + +// MAC_VERIFY +// Request: data_node_id = context_id, +// param[0]: DataBuffer — MAC tag to verify against accumulated data +// Response: status_code (SUCCESS/error) +// param[0]: bool — true if MAC is valid, false if invalid +// Effect: Calls VerifyMac(), computes MAC and compares, transitions state → IDLE +inline constexpr OperationAction MAC_VERIFY = 4; + +// MAC_GET_SIZE +// Request: data_node_id = context_id, +// no operation parameters +// Response: status_code (SUCCESS/error) +// param[0]: uint64_t — MAC output size in bytes (e.g., 32 for HMAC-SHA256) +// Effect: Queries MAC algorithm's tag size, does not affect state +inline constexpr OperationAction MAC_GET_SIZE = 5; + +// MAC_RESET +// Request: data_node_id = context_id, +// no operation parameters +// Response: status_code (SUCCESS/error) +// no output parameters +// Effect: Calls Reset(), clears intermediate MAC state, transitions state → IDLE +inline constexpr OperationAction MAC_RESET = 6; + +// MAC_SS -> TODO TBD +inline constexpr OperationAction MAC_SS = 7; + +inline constexpr OperationAction MAC_CUSTOM_OP_START = 1 << (std::numeric_limits::digits - 1); + +} // namespace mac_handler_operations +} // namespace handler +} // namespace provider +} // namespace daemon +} // namespace crypto +} // namespace score + +#endif // CRYPTO_DAEMON_PROVIDER_CRYPTO_HANDLERS_MAC_HANDLER_OPERATIONS_HPP diff --git a/score/crypto/daemon/provider/handler/src/context_data_node.cpp b/score/crypto/daemon/provider/handler/src/context_data_node.cpp new file mode 100644 index 0000000..2cf56da --- /dev/null +++ b/score/crypto/daemon/provider/handler/src/context_data_node.cpp @@ -0,0 +1,41 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/handler/context_data_node.hpp" +#include "score/crypto/daemon/data_manager/data_node.hpp" +#include "score/crypto/daemon/provider/handler/i_handler.hpp" +#include +#include +#include + +namespace score::crypto::daemon::provider::handler +{ + +ContextDataNode::ContextDataNode(std::shared_ptr handler, + const std::string& algorithm) + : DataNode(true), m_handler(std::move(handler)), m_algorithm(algorithm) +{ +} + +ContextDataNode::~ContextDataNode() = default; + +std::shared_ptr ContextDataNode::GetHandler() const +{ + return m_handler; +} + +const std::string& ContextDataNode::GetAlgorithm() const +{ + return m_algorithm; +} + +} // namespace score::crypto::daemon::provider::handler diff --git a/score/crypto/daemon/provider/handler/src/handler_utils.cpp b/score/crypto/daemon/provider/handler/src/handler_utils.cpp new file mode 100644 index 0000000..794d752 --- /dev/null +++ b/score/crypto/daemon/provider/handler/src/handler_utils.cpp @@ -0,0 +1,138 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "handler_utils.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" + +namespace score +{ +namespace crypto +{ +namespace daemon +{ +namespace provider +{ +namespace handler +{ +namespace handler_utils +{ + +Expected +ExtractBufferData(const common::RequestParameter& userData, const uint8_t*& buffer, size_t& size) noexcept +{ + // Check if data is VirtualMemoryBufferConst type variant + if (auto providerBuffer = std::get_if(&userData)) + { + if (providerBuffer->data == nullptr || providerBuffer->size == 0) + { + buffer = nullptr; + size = 0; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + + buffer = providerBuffer->data; + size = providerBuffer->size; + + return std::monostate{}; + } + + // Default: Unsupported type in variant + buffer = nullptr; + size = 0; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidDataType); +} + +Expected +ExtractOutputBufferData(common::RequestParameter& userData, uint8_t*& buffer, size_t& size) noexcept +{ + // Extract from VirtualMemoryBufferConst variant + if (auto bufferPtr = std::get_if(&userData)) + { + if (bufferPtr->data == nullptr || bufferPtr->size == 0) + { + buffer = nullptr; + size = 0; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + + buffer = static_cast(bufferPtr->data); + size = bufferPtr->size; + + return std::monostate{}; + } + + // Unsupported type in variant (e.g., CString, or integral types as output buffers) + buffer = nullptr; + size = 0; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidDataType); +} + +Expected ValidateStreamOperationSequence( + common::StreamOperationState currentState, + const std::string_view streamOperation, + common::StreamOperationState& nextState) noexcept +{ + // START operation: IDLE -> STREAM_INIT, STREAM_INIT -> STREAM_INIT (restart), STREAM_ACTIVE -> STREAM_INIT + // (restart) + if (streamOperation == "START") + { + // START is allowed from IDLE, STREAM_INIT, or STREAM_ACTIVE + // All transitions lead to STREAM_INIT + nextState = common::StreamOperationState::STREAM_INIT; + return std::monostate{}; + } + + // UPDATE operation: STREAM_INIT -> STREAM_ACTIVE, STREAM_ACTIVE -> STREAM_ACTIVE + if (streamOperation == "UPDATE") + { + if (currentState == common::StreamOperationState::STREAM_INIT) + { + // First UPDATE transitions from STREAM_INIT to STREAM_ACTIVE + nextState = common::StreamOperationState::STREAM_ACTIVE; + return std::monostate{}; + } + else if (currentState == common::StreamOperationState::STREAM_ACTIVE) + { + // Subsequent UPDATEs stay in STREAM_ACTIVE + nextState = common::StreamOperationState::STREAM_ACTIVE; + return std::monostate{}; + } + else + { + // UPDATE not allowed from IDLE + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidStreamOperation); + } + } + + // FINISH operation: STREAM_ACTIVE -> IDLE + if (streamOperation == "FINISH") + { + if (currentState != common::StreamOperationState::STREAM_ACTIVE) + { + // FINISH only allowed from STREAM_ACTIVE + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidStreamOperation); + } + nextState = common::StreamOperationState::IDLE; + return std::monostate{}; + } + + // Unknown or non-streaming operation + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); +} + +} // namespace handler_utils + +} // namespace handler +} // namespace provider +} // namespace daemon +} // namespace crypto +} // namespace score diff --git a/score/crypto/daemon/provider/handler/src/handler_utils.hpp b/score/crypto/daemon/provider/handler/src/handler_utils.hpp new file mode 100644 index 0000000..840443d --- /dev/null +++ b/score/crypto/daemon/provider/handler/src/handler_utils.hpp @@ -0,0 +1,119 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_HANDLER_HANDLER_UTILS_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_HANDLER_HANDLER_UTILS_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include +#include +#include +#include + +namespace score +{ +namespace crypto +{ +namespace daemon +{ +namespace provider +{ +namespace handler +{ + +/** + * @brief Utility functions for cryptographic handlers + * + * This namespace provides common validation and data extraction functions + * that can be reused across different handler implementations (Hash, MAC, Sign, etc.) + */ +namespace handler_utils +{ + +/** + * @brief Extract buffer data from RequestParameter structure + * + * Supports extraction of data from virtual mapped memory regions. + * Currently assumes VIR_MAPPED data type, but can be extended for other types. + * + * @param userData The RequestParameter structure containing the buffer information + * @param buffer Output pointer to the extracted buffer + * @param size Output parameter for the buffer size in bytes + * @return std::monostate on success, error code otherwise + * + * @retval std::monostate Buffer successfully extracted + * @retval score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize Invalid buffer address or zero size + * @retval score::crypto::daemon::common::DaemonErrorCode::kInvalidDataType) Unsupported data type + */ +[[nodiscard]] Expected +ExtractBufferData(const common::RequestParameter& userData, const uint8_t*& buffer, size_t& size) noexcept; + +/** + * @brief Extract non-const buffer data from RequestParameter structure + * + * Similar to ExtractBufferData but returns non-const pointer for output buffers. + * Supports extraction of data from virtual mapped memory regions. + * + * @param userData The RequestParameter structure containing the buffer information + * @param buffer Output pointer to the extracted buffer (non-const) + * @param size Output parameter for the buffer size in bytes + * @return std::monostate on success, error code otherwise + * + * @retval std::monostate Buffer successfully extracted + * @retval score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize Invalid buffer address or zero size + * @retval score::crypto::daemon::common::DaemonErrorCode::kInvalidDataType) Unsupported data type + */ +[[nodiscard]] Expected +ExtractOutputBufferData(common::RequestParameter& userData, uint8_t*& buffer, size_t& size) noexcept; + +/** + * @brief Validate and determine next state for streaming operations + * + * Enforces the stream state machine: + * - IDLE --(START)--> STREAM_INIT + * - STREAM_INIT --(START)--> STREAM_INIT (restart) + * - STREAM_INIT --(UPDATE 1+)--> STREAM_ACTIVE + * - STREAM_ACTIVE --(UPDATE)--> STREAM_ACTIVE + * - STREAM_ACTIVE --(START)--> STREAM_INIT (restart) + * - STREAM_ACTIVE --(FINISH)--> IDLE + * + * This function focuses only on streaming operation validation (START, UPDATE, FINISH) and + * decouples from single-shot operation IDs for handler-specific implementation. + * + * @param currentState The current operation state (IDLE, STREAM_INIT, or STREAM_ACTIVE) + * @param streamOperation The streaming operation being requested: "START", "UPDATE", or "FINISH" + * @param nextState Output parameter that receives the next state on SUCCESS + * @return Expected containing std::monostate on success, or the failing score::crypto::daemon::common::DaemonErrorCode + * + * @retval std::monostate Transition valid; nextState contains the new state + * @retval make_unexpected(ERROR_INVALID_STREAM_OPERATION) UPDATE/FINISH from wrong state + * @retval make_unexpected(ERROR_INVALID_PARAMETER) Unknown or non-streaming operation + * + * @note Single-shot operations are NOT validated here (handler-specific) + * @note Valid streaming operations: "START", "UPDATE", "FINISH" + */ +[[nodiscard]] Expected +ValidateStreamOperationSequence(common::StreamOperationState currentState, + std::string_view streamOperation, + common::StreamOperationState& nextState) noexcept; + +} // namespace handler_utils + +} // namespace handler +} // namespace provider +} // namespace daemon +} // namespace crypto +} // namespace score + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_HANDLER_HANDLER_UTILS_HPP diff --git a/score/crypto/daemon/provider/i_provider.hpp b/score/crypto/daemon/provider/i_provider.hpp new file mode 100644 index 0000000..717baaf --- /dev/null +++ b/score/crypto/daemon/provider/i_provider.hpp @@ -0,0 +1,131 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PROVIDER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PROVIDER_HPP + +#include "score/crypto/daemon/common/types.hpp" +#include +#include + +// Forward declarations — full headers are only pulled in by .cpp files. +namespace score::crypto::daemon::provider::handler +{ +class ICryptoHandlerFactory; +} // namespace score::crypto::daemon::provider::handler + +namespace score::crypto::daemon::key_management +{ +class IKeyFactory; +class IKeySlotHandler; +class KeyManagementService; +struct KeySlotConfig; +} // namespace score::crypto::daemon::key_management + +namespace score::crypto::daemon::provider +{ + +class ProviderManager; // Forward declaration + +/// @brief Initialization context passed to IProvider::Initialize() +/// +/// Contains the numeric provider ID (assigned by ProviderManager) and +/// the human-readable provider name (from configuration). +struct ProviderInitContext +{ + common::ProviderId numeric_id; ///< Assigned by ProviderManager (0, 1, 2, …) + common::ProviderName name; ///< Human-readable name from config/factory +}; + +/// @brief Core provider interface — lifecycle, identity, and capability accessors. +/// +/// Concrete providers subclass IProvider for lifecycle management (Initialize, +/// Shutdown) and identification (GetProviderId, GetProviderName). Functional +/// capabilities are expressed through optional virtual accessors with safe +/// default return values (nullptr / no-op). A provider overrides only the +/// methods it supports. +/// +/// ### Single-Provider Binding +/// Keys and contexts are bound to a single provider for the lifetime of the context. +/// The `KeySlotConfig::provider_ids` list specifies which providers are compatible +/// with a key slot; however, only the primary (first) provider is used for +/// context-level operations. Providers never need to know about each other. +class IProvider +{ + public: + virtual ~IProvider() = default; + + // ----------------------------------------------------------------------- + // Lifecycle + // ----------------------------------------------------------------------- + + /// @brief Initialize the provider with assigned ID and name. + /// @param ctx Initialization context containing numeric_id and name + /// @return true on success, false on failure + virtual bool Initialize(const ProviderInitContext& ctx) = 0; + + /// @brief Shutdown the provider and release all resources. + virtual void Shutdown() = 0; + + /// @brief Return the provider's unique numeric identifier. + /// @return ProviderId (uint16_t) assigned by ProviderManager + virtual common::ProviderId GetProviderId() const = 0; + + /// @brief Return the provider's human-readable name. + /// @return ProviderName (string) from configuration or factory + virtual const common::ProviderName& GetProviderName() const = 0; + + // ----------------------------------------------------------------------- + // Crypto capability (override if supported — default returns nullptr) + // ----------------------------------------------------------------------- + + /// @brief Return the factory that creates per-algorithm crypto handlers. + /// + /// Returns nullptr if the provider does not support crypto operations. + /// The factory is stateless and safe to call concurrently. + virtual std::shared_ptr GetCryptoHandlerFactory() + { + return nullptr; + } + + // ----------------------------------------------------------------------- + // Key management capability (override if supported — defaults return nullptr) + // ----------------------------------------------------------------------- + + /// Return the provider's key factory (score-interface providers: OpenSSL, etc.). + /// + /// Returns nullptr for providers that do not implement IKeyFactory (e.g., PKCS#11 + /// which creates keys through Pkcs11KeyMgmtHandler which is both Handler and IKeyFactory). + virtual std::shared_ptr GetKeyFactory() + { + return nullptr; + } + + /// @brief Return a key slot handler for the given slot configuration. + /// + /// Returns nullptr if the provider does not support key slot management. + virtual std::shared_ptr GetKeySlotHandler( + const key_management::KeySlotConfig& /*config*/) + { + return nullptr; + } + + /// @brief Inject the daemon-wide key management service. + /// + /// Called once at daemon startup before any handler is created. + /// Providers that do not support key management may ignore this (default no-op). + virtual void SetKeyManagementService(std::shared_ptr /*service*/) {} +}; + +} // namespace score::crypto::daemon::provider + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PROVIDER_HPP diff --git a/score/crypto/daemon/provider/i_provider_factory.hpp b/score/crypto/daemon/provider/i_provider_factory.hpp new file mode 100644 index 0000000..067c8d8 --- /dev/null +++ b/score/crypto/daemon/provider/i_provider_factory.hpp @@ -0,0 +1,64 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_I_PROVIDER_FACTORY_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_I_PROVIDER_FACTORY_HPP + +namespace score::crypto::daemon::provider +{ + +// Forward declaration — avoids a circular include with provider_manager.hpp. +class ProviderManager; + +/** + * @brief Abstract factory interface for creating and registering providers. + * + * Each concrete factory encapsulates the construction and registration of one + * or more related providers into a ProviderManager. Factories are registered + * externally (e.g. in daemon main()) via ProviderManager::RegisterFactory() + * and called once during ProviderManager::Initialize(). + * + * This decouples ProviderManager from concrete provider types: the manager + * never includes provider_openssl.hpp or pkcs11_provider.hpp; instead, the + * daemon bootstrapper wires the desired factories before calling Initialize(). + */ +class IProviderFactory +{ + public: + virtual ~IProviderFactory() = default; + + // Prevent copying; factories are owned via unique_ptr. + IProviderFactory(const IProviderFactory&) = delete; + IProviderFactory& operator=(const IProviderFactory&) = delete; + IProviderFactory(IProviderFactory&&) = delete; + IProviderFactory& operator=(IProviderFactory&&) = delete; + + /** + * @brief Create and register provider(s) with the given ProviderManager. + * + * The implementation is responsible for constructing provider instances and + * calling ProviderManager::RegisterProvider() for each one. If any step + * fails (module initialisation, provider construction, registration) the + * method must return false immediately without partially registering. + * + * @param manager The ProviderManager to register providers into. + * @return true if all providers were created and registered successfully. + */ + virtual bool CreateAndRegister(ProviderManager& manager) = 0; + + protected: + IProviderFactory() = default; +}; + +} // namespace score::crypto::daemon::provider + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_I_PROVIDER_FACTORY_HPP diff --git a/score/crypto/daemon/provider/pkcs11/BUILD b/score/crypto/daemon/provider/pkcs11/BUILD new file mode 100644 index 0000000..8d8fc12 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/BUILD @@ -0,0 +1,96 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "provider_pkcs11_headers", + hdrs = [ + "detail/pkcs11_algorithm_info.hpp", + "key_management/pkcs11_key_factory.hpp", + "key_management/pkcs11_key_handler.hpp", + "key_management/pkcs11_key_slot_handler.hpp", + "key_management/pkcs11_key_store.hpp", + "operations/factory/pkcs11_handler_factory.hpp", + "operations/hash/pkcs11_hash_context.hpp", + "operations/hash/pkcs11_hash_executor.hpp", + "operations/hash/pkcs11_hash_handler.hpp", + "operations/key_management/pkcs11_key_management_handler.hpp", + "operations/mac/pkcs11_mac_context.hpp", + "operations/mac/pkcs11_mac_executor.hpp", + "operations/mac/pkcs11_mac_handler.hpp", + "pkcs11_module.hpp", + "pkcs11_provider.hpp", + "pkcs11_provider_factory.hpp", + "pkcs11_session_guard.hpp", + ], + includes = ["."], + visibility = [ + "//:__subpackages__", + ], +) + +cc_library( + name = "provider_pkcs11_library", + srcs = [ + "key_management/pkcs11_key_factory.cpp", + "key_management/pkcs11_key_handler.cpp", + "key_management/pkcs11_key_slot_handler.cpp", + "key_management/pkcs11_key_store.cpp", + "operations/factory/pkcs11_handler_factory.cpp", + "operations/hash/pkcs11_hash_executor.cpp", + "operations/hash/pkcs11_hash_handler.cpp", + "operations/key_management/pkcs11_key_management_handler.cpp", + "operations/mac/pkcs11_mac_executor.cpp", + "operations/mac/pkcs11_mac_handler.cpp", + "pkcs11_module.cpp", + "pkcs11_provider.cpp", + ], + includes = ["."], + linkstatic = True, + visibility = ["//:__subpackages__"], + deps = [ + ":provider_pkcs11_headers", + "//score/crypto/daemon/common", + "//score/crypto/daemon/common:algorithm_info", + "//score/crypto/daemon/key_management", + "//score/crypto/daemon/provider:provider_headers", + "//score/crypto/daemon/provider/executors:key_mgmt_executor", + "//score/crypto/daemon/provider/handler:handler_utils_impl", + "//score/crypto/daemon/provider/handler:hash_handler_operations", + "//score/crypto/daemon/provider/handler:mac_handler_operations", + "//third_party/soft_hsm:libsofthsm_shared", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "pkcs11_token_config", + hdrs = ["pkcs11_token_config.hpp"], + visibility = ["//:__subpackages__"], +) + +cc_library( + name = "provider_pkcs11_factory", + srcs = [ + "pkcs11_provider_factory.cpp", + "pkcs11_token_config.cpp", + ], + hdrs = ["pkcs11_provider_factory.hpp"], + visibility = ["//:__subpackages__"], + deps = [ + ":pkcs11_token_config", + ":provider_pkcs11_library", + "//score/crypto/daemon/provider:provider_headers", + ], +) diff --git a/score/crypto/daemon/provider/pkcs11/detail/pkcs11_algorithm_info.hpp b/score/crypto/daemon/provider/pkcs11/detail/pkcs11_algorithm_info.hpp new file mode 100644 index 0000000..5c2edde --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/detail/pkcs11_algorithm_info.hpp @@ -0,0 +1,169 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_DETAIL_PKCS11_ALGORITHM_INFO_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_DETAIL_PKCS11_ALGORITHM_INFO_HPP + +#include "score/crypto/daemon/common/types.hpp" + +#include +#include + +#include +#include +#include + +namespace score::crypto::daemon::provider::pkcs11::detail +{ + +/// @brief PKCS#11 algorithm parameters derived from a daemon algorithm identifier. +struct Pkcs11AlgoInfo +{ + CK_KEY_TYPE ck_key_type{CKK_GENERIC_SECRET}; ///< CKA_KEY_TYPE attribute value + CK_MECHANISM_TYPE gen_mechanism{CKM_GENERIC_SECRET_KEY_GEN}; ///< For C_GenerateKey + CK_ULONG value_len{0U}; ///< CKA_VALUE_LEN in bytes +}; + +/// @brief Lookup table entry mapping algorithm identifier substring to PKCS#11 parameters. +struct Pkcs11AlgoEntry +{ + std::string_view algorithm; + Pkcs11AlgoInfo info; +}; + +/// @brief Lookup table for algorithm identifier to PKCS#11 parameters mapping. +/// +/// Each entry maps a daemon algorithm identifier substring (case-sensitive, uppercase +/// expected) to its corresponding PKCS#11 parameters. Used by LookupAlgorithm for +/// O(n) linear search; extend this table when adding new algorithm support. +inline constexpr Pkcs11AlgoEntry kAlgoEntries[] = { + // HMAC family — generic secret keys; size varies by hash length + {"HMAC-SHA256", {CKK_GENERIC_SECRET, CKM_GENERIC_SECRET_KEY_GEN, 32U}}, + {"HMAC-SHA384", {CKK_GENERIC_SECRET, CKM_GENERIC_SECRET_KEY_GEN, 48U}}, + {"HMAC-SHA512", {CKK_GENERIC_SECRET, CKM_GENERIC_SECRET_KEY_GEN, 64U}}, + // AES family + {"AES-128", {CKK_AES, CKM_AES_KEY_GEN, 16U}}, + {"AES-192", {CKK_AES, CKM_AES_KEY_GEN, 24U}}, + {"AES-256", {CKK_AES, CKM_AES_KEY_GEN, 32U}}, +}; + +/// @brief Map a daemon algorithm identifier string to PKCS#11 parameters. +/// +/// Returns an empty optional for unknown or unsupported algorithms. +/// Case-sensitive substring matching (uppercase expected, matching the AlgorithmId +/// values used by the daemon). +/// +/// Adding a new algorithm: extend kAlgoEntries and add a matching entry to +/// OpenSslKeyHandler::DetermineKeySize() for cross-provider consistency. +[[nodiscard]] inline std::optional LookupAlgorithm(const common::AlgorithmId& algo) noexcept +{ + for (const auto& entry : kAlgoEntries) + { + if (algo.find(entry.algorithm) != std::string::npos) + { + return entry.info; + } + } + return std::nullopt; +} + +// --------------------------------------------------------------------------- +// Hash algorithm → CK_MECHANISM_TYPE +// --------------------------------------------------------------------------- + +struct Pkcs11HashMechanism +{ + std::string_view name; + CK_MECHANISM_TYPE mechanism; +}; + +inline constexpr Pkcs11HashMechanism kHashMechanisms[] = { + {"SHA256", CKM_SHA256}, + {"SHA384", CKM_SHA384}, + {"SHA512", CKM_SHA512}, + {"SHA224", CKM_SHA224}, + {"SHA1", CKM_SHA_1}, + {"MD5", CKM_MD5}, +}; + +/// @brief Look up the PKCS#11 mechanism type for a hash algorithm. +[[nodiscard]] inline CK_MECHANISM_TYPE LookupHashMechanism(std::string_view algorithm) noexcept +{ + for (const auto& entry : kHashMechanisms) + { + if (entry.name == algorithm) + { + return entry.mechanism; + } + } + return CK_UNAVAILABLE_INFORMATION; +} + +// --------------------------------------------------------------------------- +// MAC algorithm → CK_MECHANISM_TYPE +// --------------------------------------------------------------------------- + +struct Pkcs11MacMechanism +{ + std::string_view name; + CK_MECHANISM_TYPE mechanism; +}; + +inline constexpr Pkcs11MacMechanism kMacMechanisms[] = { + {"HMAC-SHA256", CKM_SHA256_HMAC}, + {"HMAC-SHA384", CKM_SHA384_HMAC}, + {"HMAC-SHA512", CKM_SHA512_HMAC}, +}; + +/// @brief Look up the PKCS#11 mechanism type for a MAC algorithm. +[[nodiscard]] inline CK_MECHANISM_TYPE LookupMacMechanism(std::string_view algorithm) noexcept +{ + for (const auto& entry : kMacMechanisms) + { + if (entry.name == algorithm) + { + return entry.mechanism; + } + } + return CK_UNAVAILABLE_INFORMATION; +} + +/// @brief Decode a hex string (e.g. "0102abcd") into a byte vector. +/// +/// Returns an empty vector if the input is empty or has odd length. +inline std::vector HexDecode(std::string_view hex) noexcept +{ + if (hex.empty() || (hex.size() % 2U != 0U)) + { + return {}; + } + std::vector out; + out.reserve(hex.size() / 2U); + for (std::size_t i = 0U; i < hex.size(); i += 2U) + { + const auto nibble = [](char c) -> uint8_t { + if (c >= '0' && c <= '9') + return static_cast(c - '0'); + if (c >= 'a' && c <= 'f') + return static_cast(c - 'a' + 10); + if (c >= 'A' && c <= 'F') + return static_cast(c - 'A' + 10); + return 0U; + }; + out.push_back(static_cast((nibble(hex[i]) << 4U) | nibble(hex[i + 1U]))); + } + return out; +} + +} // namespace score::crypto::daemon::provider::pkcs11::detail + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_DETAIL_PKCS11_ALGORITHM_INFO_HPP diff --git a/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_factory.cpp b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_factory.cpp new file mode 100644 index 0000000..b72c2c9 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_factory.cpp @@ -0,0 +1,233 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_factory.hpp" + +#include "score/crypto/daemon/provider/pkcs11/detail/pkcs11_algorithm_info.hpp" +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.hpp" +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_session_guard.hpp" + +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +// ========== Private definition for LOG_PREFIX ========== +#ifndef LOG_PREFIX +#define LOG_PREFIX "[Pkcs11KeyFactory] " +#endif + +// --------------------------------------------------------------------------- +// Permission -> CK attribute mapping +// --------------------------------------------------------------------------- + +/// @brief Per-usage CK_BBOOL values derived from a KeyOperationPermission bitmask. +struct KeyUsageFlags +{ + CK_BBOOL ck_encrypt{CK_FALSE}; + CK_BBOOL ck_decrypt{CK_FALSE}; + CK_BBOOL ck_sign{CK_FALSE}; ///< covers kSign and kMac (PKCS#11 CKA_SIGN = MAC for symmetric) + CK_BBOOL ck_verify{CK_FALSE}; ///< covers kVerify and kMac + CK_BBOOL ck_wrap{CK_FALSE}; + CK_BBOOL ck_unwrap{CK_FALSE}; + CK_BBOOL ck_derive{CK_FALSE}; +}; + +static KeyUsageFlags BuildUsageFlags(score::mw::crypto::KeyOperationPermission perm) noexcept +{ + using P = score::mw::crypto::KeyOperationPermission; + auto has = [perm](P bit) -> CK_BBOOL { + return ((perm & bit) != P::kNone) ? CK_TRUE : CK_FALSE; + }; + KeyUsageFlags f; + f.ck_encrypt = has(P::kEncrypt); + f.ck_decrypt = has(P::kDecrypt); + // kMac maps to CKA_SIGN + CKA_VERIFY for symmetric keys (e.g. HMAC, AES-CMAC). + f.ck_sign = ((has(P::kSign) == CK_TRUE) || (has(P::kMac) == CK_TRUE)) ? CK_TRUE : CK_FALSE; + f.ck_verify = ((has(P::kVerify) == CK_TRUE) || (has(P::kMac) == CK_TRUE)) ? CK_TRUE : CK_FALSE; + f.ck_wrap = has(P::kWrap); + f.ck_unwrap = has(P::kUnwrap); + f.ck_derive = has(P::kDerive); + return f; +} + +Pkcs11KeyFactory::Pkcs11KeyFactory(std::weak_ptr provider, + std::weak_ptr module, + std::shared_ptr key_store) + : m_provider{std::move(provider)}, m_module{std::move(module)}, m_key_store{std::move(key_store)} +{ +} + +score::crypto::Expected +Pkcs11KeyFactory::GenerateKey(const key_management::KeyGenerationRequest& request) +{ + auto provider = m_provider.lock(); + if (!provider) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + const auto algo_info = detail::LookupAlgorithm(request.algorithm); + if (!algo_info.has_value()) + { + std::cerr << LOG_PREFIX << "GenerateKey: unsupported algorithm '" << request.algorithm << "'\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + const Pkcs11HandlerRequirements reqs{Pkcs11SessionType::ReadWrite, Pkcs11TokenAuthState::User}; + Pkcs11SessionGuard guard(*provider, reqs); + if (!guard) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kProviderBusy); + } + const CK_SESSION_HANDLE session = guard.get(); + + CK_BBOOL ck_true = CK_TRUE; + CK_BBOOL ck_false = CK_FALSE; + CK_BBOOL ck_extractable = + score::mw::crypto::HasPermission(request.permissions, score::mw::crypto::KeyOperationPermission::kExport) + ? CK_TRUE + : CK_FALSE; + CK_ULONG val_len = algo_info->value_len; + CK_OBJECT_CLASS key_class = CKO_SECRET_KEY; + CK_KEY_TYPE key_type = algo_info->ck_key_type; + KeyUsageFlags flags = BuildUsageFlags(request.permissions); + + CK_ATTRIBUTE attrs[] = { + {CKA_CLASS, &key_class, sizeof(key_class)}, + {CKA_KEY_TYPE, &key_type, sizeof(key_type)}, + {CKA_TOKEN, &ck_false, sizeof(CK_BBOOL)}, + {CKA_SENSITIVE, &ck_true, sizeof(CK_BBOOL)}, + {CKA_EXTRACTABLE, &ck_extractable, sizeof(CK_BBOOL)}, + {CKA_ENCRYPT, &flags.ck_encrypt, sizeof(CK_BBOOL)}, + {CKA_DECRYPT, &flags.ck_decrypt, sizeof(CK_BBOOL)}, + {CKA_SIGN, &flags.ck_sign, sizeof(CK_BBOOL)}, + {CKA_VERIFY, &flags.ck_verify, sizeof(CK_BBOOL)}, + {CKA_WRAP, &flags.ck_wrap, sizeof(CK_BBOOL)}, + {CKA_UNWRAP, &flags.ck_unwrap, sizeof(CK_BBOOL)}, + {CKA_DERIVE, &flags.ck_derive, sizeof(CK_BBOOL)}, + {CKA_VALUE_LEN, &val_len, sizeof(CK_ULONG)}, + }; + + CK_MECHANISM mechanism{algo_info->gen_mechanism, nullptr, 0U}; + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + auto module = m_module.lock(); + if (!module) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + CK_FUNCTION_LIST* fns = module->GetFunctionList(); + if (fns == nullptr) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + const CK_RV rv = fns->C_GenerateKey(session, &mechanism, attrs, sizeof(attrs) / sizeof(attrs[0]), &object); + if (rv != CKR_OK) + { + std::cerr << LOG_PREFIX << "C_GenerateKey failed: rv=" << static_cast(rv) << '\n'; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kOperationFailed); + } + + const auto handle = m_key_store->Register( + session, object, request.algorithm, static_cast(val_len), request.permissions); + + // Session intentionally NOT released here. PKCS#11 §5.7: session objects + // (CKA_TOKEN=false) are destroyed when the creating session is closed. + // The session must remain open for the entire key lifetime. It is released + // in Pkcs11KeyStore::Release() after C_DestroyObject. + static_cast(guard.release()); + return std::make_shared(m_key_store, std::move(handle)); +} + +score::crypto::Expected +Pkcs11KeyFactory::ImportKey(const key_management::KeyImportRequest& request) +{ + auto provider = m_provider.lock(); + if (!provider || (request.key_data == nullptr) || (request.key_data_size == 0U)) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + const auto algo_info = detail::LookupAlgorithm(request.algorithm); + if (!algo_info.has_value()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + const Pkcs11HandlerRequirements reqs{Pkcs11SessionType::ReadWrite, Pkcs11TokenAuthState::User}; + Pkcs11SessionGuard guard(*provider, reqs); + if (!guard) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kProviderBusy); + } + const CK_SESSION_HANDLE session = guard.get(); + + CK_BBOOL ck_true = CK_TRUE; + CK_BBOOL ck_false = CK_FALSE; + CK_BBOOL ck_extractable = + score::mw::crypto::HasPermission(request.permissions, score::mw::crypto::KeyOperationPermission::kExport) + ? CK_TRUE + : CK_FALSE; + CK_OBJECT_CLASS key_class = CKO_SECRET_KEY; + CK_KEY_TYPE key_type = algo_info->ck_key_type; + // MISRA C++:2023 Rule 8.2.3 deviation — PKCS#11 C API (CK_ATTRIBUTE) requires non-const pValue. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + CK_BYTE_PTR key_value = + const_cast(static_cast(static_cast(request.key_data))); + KeyUsageFlags flags = BuildUsageFlags(request.permissions); + + CK_ATTRIBUTE attrs[] = { + {CKA_CLASS, &key_class, sizeof(key_class)}, + {CKA_KEY_TYPE, &key_type, sizeof(key_type)}, + {CKA_TOKEN, &ck_false, sizeof(CK_BBOOL)}, + {CKA_SENSITIVE, &ck_true, sizeof(CK_BBOOL)}, + {CKA_EXTRACTABLE, &ck_extractable, sizeof(CK_BBOOL)}, + {CKA_ENCRYPT, &flags.ck_encrypt, sizeof(CK_BBOOL)}, + {CKA_DECRYPT, &flags.ck_decrypt, sizeof(CK_BBOOL)}, + {CKA_SIGN, &flags.ck_sign, sizeof(CK_BBOOL)}, + {CKA_VERIFY, &flags.ck_verify, sizeof(CK_BBOOL)}, + {CKA_WRAP, &flags.ck_wrap, sizeof(CK_BBOOL)}, + {CKA_UNWRAP, &flags.ck_unwrap, sizeof(CK_BBOOL)}, + {CKA_DERIVE, &flags.ck_derive, sizeof(CK_BBOOL)}, + {CKA_VALUE, key_value, static_cast(request.key_data_size)}, + }; + + auto module = m_module.lock(); + if (!module) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + CK_FUNCTION_LIST* fns = module->GetFunctionList(); + if (fns == nullptr) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + CK_OBJECT_HANDLE object = CK_INVALID_HANDLE; + const CK_RV rv = fns->C_CreateObject(session, attrs, sizeof(attrs) / sizeof(attrs[0]), &object); + if (rv != CKR_OK) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kOperationFailed); + } + + const auto handle = + m_key_store->Register(session, object, request.algorithm, request.key_data_size, request.permissions); + + // Session intentionally NOT released. See GenerateKey rationale above. + static_cast(guard.release()); + return std::make_shared(m_key_store, std::move(handle)); +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_factory.hpp b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_factory.hpp new file mode 100644 index 0000000..ff80a88 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_factory.hpp @@ -0,0 +1,77 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_KEY_MANAGEMENT_PKCS11_KEY_FACTORY_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_KEY_MANAGEMENT_PKCS11_KEY_FACTORY_HPP + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_factory.hpp" + +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +class Pkcs11Provider; +class Pkcs11Module; +class Pkcs11KeyStore; + +/// IKeyFactory implementation for PKCS#11 key generation and import. +/// +/// Pkcs11KeyFactory generates and imports PKCS#11 session keys (CKA_TOKEN=false). +/// It injects a weak_ptr for HSM access and a shared_ptr +/// for opaque-id allocation and session-key storage. +/// +/// Generated/imported keys are wrapped in Pkcs11KeyHandler instances that hold +/// weak_ptr for Release callbacks. +class Pkcs11KeyFactory : public key_management::IKeyFactory +{ + public: + /// Constructor. + /// + /// \param provider weak_ptr to the parent Pkcs11Provider (for AcquireSession, session management) + /// \param module weak_ptr to the Pkcs11Module (for GetFunctionList access) + /// \param key_store shared_ptr to the Pkcs11KeyStore (for opaque_id allocation and lookup) + Pkcs11KeyFactory(std::weak_ptr provider, + std::weak_ptr module, + std::shared_ptr key_store); + + ~Pkcs11KeyFactory() override = default; + + Pkcs11KeyFactory(const Pkcs11KeyFactory&) = delete; + Pkcs11KeyFactory& operator=(const Pkcs11KeyFactory&) = delete; + Pkcs11KeyFactory(Pkcs11KeyFactory&&) = delete; + Pkcs11KeyFactory& operator=(Pkcs11KeyFactory&&) = delete; + + /// Generate a PKCS#11 session key via C_GenerateKey. + /// + /// Acquires a session from the provider, generates the key, and registers it in the store. + [[nodiscard]] score::crypto::Expected + GenerateKey(const key_management::KeyGenerationRequest& request) override; + + /// Import raw key material via C_CreateObject (session object, CKA_TOKEN=false). + /// + /// Acquires a session from the provider, creates an object, and registers it in the store. + [[nodiscard]] score::crypto::Expected + ImportKey(const key_management::KeyImportRequest& request) override; + + private: + std::weak_ptr m_provider; + std::weak_ptr m_module; + std::shared_ptr m_key_store; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_KEY_MANAGEMENT_PKCS11_KEY_FACTORY_HPP diff --git a/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.cpp b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.cpp new file mode 100644 index 0000000..7456aa1 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.cpp @@ -0,0 +1,85 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.hpp" +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp" + +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +Pkcs11KeyHandler::Pkcs11KeyHandler(std::weak_ptr key_store, + key_management::ProviderKeyHandle key_handle) noexcept + : m_key_store{std::move(key_store)}, m_key_handle{std::move(key_handle)}, m_released{false} +{ +} + +Pkcs11KeyHandler::~Pkcs11KeyHandler() +{ + std::cout << "[PKCS11_KEY_HANDLER] Release Key\n"; + static_cast(Release()); +} + +const key_management::ProviderKeyHandle& Pkcs11KeyHandler::GetHandle() const noexcept +{ + return m_key_handle; +} + +common::ProviderId Pkcs11KeyHandler::GetProviderId() const noexcept +{ + return m_key_handle.provider_id; +} + +score::crypto::Expected Pkcs11KeyHandler::Release() +{ + if (m_released.exchange(true)) + { + return std::monostate{}; // idempotent — already released by another thread + } + + auto key_store = m_key_store.lock(); + if (key_store == nullptr) + { + // Key store was destroyed before the key — nothing to clean up. + return std::monostate{}; + } + return key_store->Release(m_key_handle.opaque_id, m_key_handle); +} + +std::pair Pkcs11KeyHandler::GetSessionKey() const noexcept +{ + auto key_store = m_key_store.lock(); + if (key_store == nullptr) + { + return {CK_INVALID_HANDLE, CK_INVALID_HANDLE}; + } + return key_store->Lookup(m_key_handle.opaque_id); +} + +Pkcs11KeyStore::ResolvedKey Pkcs11KeyHandler::ResolveObject(CK_SESSION_HANDLE handler_session) const noexcept +{ + auto key_store = m_key_store.lock(); + if (key_store == nullptr) + { + return {}; + } + return key_store->ResolveObject(m_key_handle.opaque_id, handler_session); +} + +score::crypto::Expected +Pkcs11KeyHandler::Export() const +{ + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kKeyOperationNotPermitted); +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.hpp b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.hpp new file mode 100644 index 0000000..a8a7404 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.hpp @@ -0,0 +1,91 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_KEY_MANAGEMENT_PKCS11_KEY_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_KEY_MANAGEMENT_PKCS11_KEY_HANDLER_HPP + +#include "score/crypto/daemon/key_management/interfaces/i_key_handler.hpp" +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp" + +#include + +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +/// IKeyHandler implementation for PKCS#11 keys. +/// +/// Holds a weak reference to the Pkcs11KeyStore that owns the opaque-id +/// map. On Release(), calls store->Release() to destroy the PKCS#11 object +/// and erase the map entry. Export() returns kNotPermitted for all PKCS#11 keys. +class Pkcs11KeyHandler final : public key_management::IKeyHandler +{ + public: + Pkcs11KeyHandler(std::weak_ptr key_store, key_management::ProviderKeyHandle key_handle) noexcept; + + ~Pkcs11KeyHandler() override; + + Pkcs11KeyHandler(const Pkcs11KeyHandler&) = delete; + Pkcs11KeyHandler& operator=(const Pkcs11KeyHandler&) = delete; + Pkcs11KeyHandler(Pkcs11KeyHandler&&) = delete; + Pkcs11KeyHandler& operator=(Pkcs11KeyHandler&&) = delete; + + [[nodiscard]] const key_management::ProviderKeyHandle& GetHandle() const noexcept override; + + [[nodiscard]] common::ProviderId GetProviderId() const noexcept override; + + [[nodiscard]] score::crypto::Expected Release() + override; + + [[nodiscard]] score::crypto::Expected + Export() const override; + + /// Direct access to PKCS#11 session key without opaque_id round-trip. + [[nodiscard]] std::pair GetSessionKey() const noexcept; + + /// Resolve a usable PKCS#11 object handle for the given handler session. + /// + /// For session-object keys (GenerateKey / ImportKey) the stored handle is + /// returned directly — it is valid on any session. + /// + /// For token-object keys (LoadKey) C_FindObjects is re-run on handler_session + /// using the stored SearchTemplate, returning a fresh session-local handle. + /// Multiple independent handlers may call this concurrently on the same key. + /// + /// Returns CK_INVALID_HANDLE on any error (key not found, module gone, etc.). + /// Resolve a PKCS#11 key for use on a crypto operation. + /// + /// For session-object keys: returns the creating session + stored handle + /// and acquires the per-key mutex exclusively. The caller must hold the + /// returned ResolvedKey for the full duration of the crypto operation + /// (C_SignInit through C_SignFinal) to serialize concurrent access. + /// + /// For token-object keys: re-runs C_FindObjects on handler_session, + /// returning a session-local handle with no lock. + /// + /// Returns an invalid ResolvedKey on any error. + [[nodiscard]] Pkcs11KeyStore::ResolvedKey ResolveObject(CK_SESSION_HANDLE handler_session) const noexcept; + + private: + std::weak_ptr m_key_store; + key_management::ProviderKeyHandle m_key_handle; + std::atomic m_released; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_KEY_MANAGEMENT_PKCS11_KEY_HANDLER_HPP diff --git a/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_slot_handler.cpp b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_slot_handler.cpp new file mode 100644 index 0000000..fa469b4 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_slot_handler.cpp @@ -0,0 +1,281 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_slot_handler.hpp" + +#include "score/crypto/daemon/key_management/detail/slot_info_builder.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_management_operations.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" +#include "score/crypto/daemon/key_management/slot/deployment_loader.hpp" +#include "score/crypto/daemon/provider/pkcs11/detail/pkcs11_algorithm_info.hpp" +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.hpp" +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp" + +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +// ============================================================================= +// Pkcs11KeySlotHandler +// ============================================================================= + +Pkcs11KeySlotHandler::Pkcs11KeySlotHandler(std::shared_ptr provider, + std::weak_ptr module, + std::shared_ptr key_store) + : m_provider{std::move(provider)}, m_module{std::move(module)}, m_key_store{std::move(key_store)} +{ +} + +score::crypto::Expected +Pkcs11KeySlotHandler::LoadKey(const key_management::KeySlotConfig& slot) +{ + using key_management::deployment_keys::kPkcs11Label; + using key_management::deployment_keys::kPkcs11ObjectClass; + using key_management::deployment_keys::kPkcs11ObjectId; + + if (!m_provider) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + // Load deployment info to get PKCS#11 object identifiers. + auto deploy_result = key_management::DeploymentLoader::Load(slot.deployment_path, slot.deployment_format); + if (!deploy_result.has_value()) + { + return score::crypto::make_unexpected(deploy_result.error()); + } + const auto& key_props = deploy_result.value().key_properties; + + const auto label_it = key_props.find(std::string{kPkcs11Label}); + const auto id_it = key_props.find(std::string{kPkcs11ObjectId}); + const auto class_it = key_props.find(std::string{kPkcs11ObjectClass}); + + if (label_it == key_props.end() && id_it == key_props.end()) + { + std::cerr << LOG_PREFIX << "LoadKey: slot '" << slot.slot_name + << "' has neither pkcs11.label nor pkcs11.object_id in deployment info\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + // Determine object class (default: CKO_SECRET_KEY). + CK_OBJECT_CLASS obj_class = CKO_SECRET_KEY; + if (class_it != key_props.end()) + { + const auto& cls_str = class_it->second; + if (cls_str == "private_key") + { + obj_class = CKO_PRIVATE_KEY; + } + else if (cls_str == "public_key") + { + obj_class = CKO_PUBLIC_KEY; + } + // else: default secret_key + } + + // Build the search template. + std::vector tmpl; + tmpl.push_back({CKA_CLASS, &obj_class, sizeof(obj_class)}); + + // Label attribute (must outlive the C_FindObjects call). + std::string label_value; + if (label_it != key_props.end()) + { + label_value = label_it->second; + // MISRA C++:2023 Rule 8.2.3 deviation — PKCS#11 C API (CK_ATTRIBUTE) requires non-const pValue. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + tmpl.push_back({CKA_LABEL, const_cast(label_value.data()), static_cast(label_value.size())}); + } + + // Object ID attribute (must outlive the C_FindObjects call). + std::vector id_bytes; + if (id_it != key_props.end()) + { + id_bytes = detail::HexDecode(id_it->second); + if (!id_bytes.empty()) + { + tmpl.push_back({CKA_ID, id_bytes.data(), static_cast(id_bytes.size())}); + } + } + + // Acquire RO session (User auth needed for secret key objects). + const Pkcs11HandlerRequirements reqs{Pkcs11SessionType::ReadOnly, Pkcs11TokenAuthState::User}; + auto session_result = m_provider->AcquireSession(reqs); + if (!session_result.has_value()) + { + std::cerr << LOG_PREFIX << "LoadKey: failed to acquire RO session for slot '" << slot.slot_name << "'\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kProviderBusy); + } + const CK_SESSION_HANDLE session = session_result.value(); + + auto module = m_module.lock(); + if (!module) + { + m_provider->ReleaseSession(session, reqs); + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + CK_FUNCTION_LIST* fns = module->GetFunctionList(); + if (fns == nullptr) + { + m_provider->ReleaseSession(session, reqs); + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + CK_RV rv = fns->C_FindObjectsInit(session, tmpl.data(), static_cast(tmpl.size())); + if (rv != CKR_OK) + { + std::cerr << LOG_PREFIX << "C_FindObjectsInit failed: rv=" << static_cast(rv) << '\n'; + m_provider->ReleaseSession(session, reqs); + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kKeySlotEmpty); + } + + CK_OBJECT_HANDLE found_object = CK_INVALID_HANDLE; + CK_ULONG found_count = 0U; + rv = fns->C_FindObjects(session, &found_object, 1U, &found_count); + static_cast(fns->C_FindObjectsFinal(session)); // always finalise + + if (rv != CKR_OK || found_count == 0U || found_object == CK_INVALID_HANDLE) + { + std::cerr << LOG_PREFIX << "C_FindObjects: object not found for slot '" << slot.slot_name << "'\n"; + m_provider->ReleaseSession(session, reqs); + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kKeySlotEmpty); + } + + // Retrieve CKA_VALUE_LEN (key size in bytes). + CK_ULONG value_len = 0U; + CK_ATTRIBUTE value_len_attr{CKA_VALUE_LEN, &value_len, sizeof(value_len)}; + rv = fns->C_GetAttributeValue(session, found_object, &value_len_attr, 1U); + // Some tokens don't support CKA_VALUE_LEN -- fall back to algorithm-derived size. + if (rv != CKR_OK) + { + const auto algo_info = detail::LookupAlgorithm(slot.algorithm); + value_len = algo_info.has_value() ? static_cast(algo_info->value_len) : 0U; + } + + // Store the search template so any handler can independently locate the key + // via Pkcs11KeyStore::ResolveObject() on its own session. + SearchTemplate search_tmpl; + search_tmpl.label = (label_it != key_props.end()) ? label_it->second : std::string{}; + search_tmpl.id = id_bytes; + search_tmpl.obj_class = obj_class; + + const auto handle = + m_key_store->RegisterTokenObject(search_tmpl, slot.algorithm, static_cast(value_len)); + + m_provider->ReleaseSession(session, reqs); + + return std::make_shared(m_key_store, std::move(handle)); +} + +score::crypto::Expected +Pkcs11KeySlotHandler::GetSlotState(const key_management::KeySlotConfig& slot) +{ + using key_management::deployment_keys::kPkcs11Label; + using key_management::deployment_keys::kPkcs11ObjectId; + + if (!m_provider) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + // Load deployment info to get PKCS#11 object identifiers. + auto deploy_result = key_management::DeploymentLoader::Load(slot.deployment_path, slot.deployment_format); + if (!deploy_result.has_value()) + { + return score::mw::crypto::KeySlotState::kEmpty; + } + const auto& key_props = deploy_result.value().key_properties; + + const auto label_it = key_props.find(std::string{kPkcs11Label}); + const auto id_it = key_props.find(std::string{kPkcs11ObjectId}); + + if (label_it == key_props.end() && id_it == key_props.end()) + { + return score::mw::crypto::KeySlotState::kEmpty; + } + + const Pkcs11HandlerRequirements reqs{Pkcs11SessionType::ReadOnly, Pkcs11TokenAuthState::User}; + auto session_result = m_provider->AcquireSession(reqs); + if (!session_result.has_value()) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kProviderBusy); + } + const CK_SESSION_HANDLE session = session_result.value(); + + auto module = m_module.lock(); + if (!module) + { + m_provider->ReleaseSession(session, reqs); + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + CK_FUNCTION_LIST* fns = module->GetFunctionList(); + if (fns == nullptr) + { + m_provider->ReleaseSession(session, reqs); + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + CK_OBJECT_CLASS obj_class = CKO_SECRET_KEY; + std::vector tmpl; + tmpl.push_back({CKA_CLASS, &obj_class, sizeof(obj_class)}); + + std::string label_value; + if (label_it != key_props.end()) + { + label_value = label_it->second; + // MISRA C++:2023 Rule 8.2.3 deviation — PKCS#11 C API (CK_ATTRIBUTE) requires non-const pValue. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + tmpl.push_back({CKA_LABEL, const_cast(label_value.data()), static_cast(label_value.size())}); + } + + std::vector id_bytes; + if (id_it != key_props.end()) + { + id_bytes = detail::HexDecode(id_it->second); + if (!id_bytes.empty()) + { + tmpl.push_back({CKA_ID, id_bytes.data(), static_cast(id_bytes.size())}); + } + } + + const CK_RV rv_init = fns->C_FindObjectsInit(session, tmpl.data(), static_cast(tmpl.size())); + score::mw::crypto::KeySlotState state = score::mw::crypto::KeySlotState::kEmpty; + if (rv_init == CKR_OK) + { + CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; + CK_ULONG count = 0U; + if (fns->C_FindObjects(session, &obj, 1U, &count) == CKR_OK && count > 0U) + { + state = score::mw::crypto::KeySlotState::kOccupied; + } + static_cast(fns->C_FindObjectsFinal(session)); + } + + m_provider->ReleaseSession(session, reqs); + return state; +} + +score::crypto::Expected +Pkcs11KeySlotHandler::GetSlotInfo(const key_management::KeySlotConfig& slot) +{ + auto state = GetSlotState(slot); + if (!state.has_value()) + { + return score::crypto::make_unexpected(state.error()); + } + return key_management::detail::BuildKeySlotInfo(slot, state.value()); +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_slot_handler.hpp b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_slot_handler.hpp new file mode 100644 index 0000000..84f2123 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_slot_handler.hpp @@ -0,0 +1,77 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_KEY_MANAGEMENT_PKCS11_KEY_SLOT_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_KEY_MANAGEMENT_PKCS11_KEY_SLOT_HANDLER_HPP + +#include "score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" + +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +// Forward declarations +class Pkcs11Provider; +class Pkcs11Module; +class Pkcs11KeyStore; + +/// PKCS#11 implementation of IKeySlotHandler. +/// +/// Manages key slots that reside as token objects on a PKCS#11 token. +/// Slot identification is driven by slot.provider_params: +/// - kPkcs11Label → CKA_LABEL in C_FindObjects template +/// - kPkcs11ObjectId → CKA_ID (hex string) in C_FindObjects template +/// - kPkcs11ObjectClass → CKA_CLASS (default: CKO_SECRET_KEY) +/// +/// LoadKey verifies the token object exists, stores its search template in the +/// Pkcs11KeyStore, then releases the discovery session. Crypto handlers call +/// Pkcs11KeyHandler::ResolveObject() with their own session to obtain a +/// handle at bind time. The returned IKeyHandler delegates release to the store. +class Pkcs11KeySlotHandler final : public key_management::IKeySlotHandler +{ + public: + Pkcs11KeySlotHandler(std::shared_ptr provider, + std::weak_ptr module, + std::shared_ptr key_store); + + ~Pkcs11KeySlotHandler() override = default; + + Pkcs11KeySlotHandler(const Pkcs11KeySlotHandler&) = delete; + Pkcs11KeySlotHandler& operator=(const Pkcs11KeySlotHandler&) = delete; + Pkcs11KeySlotHandler(Pkcs11KeySlotHandler&&) = delete; + Pkcs11KeySlotHandler& operator=(Pkcs11KeySlotHandler&&) = delete; + + /// Find the token object matching slot.provider_params and return a key handler. + /// + /// Uses C_FindObjectsInit / C_FindObjects / C_FindObjectsFinal. + [[nodiscard]] score::crypto::Expected + LoadKey(const key_management::KeySlotConfig& slot) override; + + /// Return kOccupied if the token object exists, kEmpty otherwise. + [[nodiscard]] score::crypto::Expected + GetSlotState(const key_management::KeySlotConfig& slot) override; + + /// Return slot metadata from config and token object attributes. + [[nodiscard]] score::crypto::Expected + GetSlotInfo(const key_management::KeySlotConfig& slot) override; + + private: + std::shared_ptr m_provider; + std::weak_ptr m_module; + std::shared_ptr m_key_store; + + static constexpr std::string_view LOG_PREFIX = "[PKCS11_KEY_SLOT_HANDLER] "; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_KEY_MANAGEMENT_PKCS11_KEY_SLOT_HANDLER_HPP diff --git a/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.cpp b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.cpp new file mode 100644 index 0000000..ffe00a5 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.cpp @@ -0,0 +1,247 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp" + +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp" + +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +// ========== Private definition for LOG_PREFIX ========== +#ifndef LOG_PREFIX +#define LOG_PREFIX "[Pkcs11KeyStore] " +#endif + +Pkcs11KeyStore::Pkcs11KeyStore(std::weak_ptr provider, std::weak_ptr module) + : m_provider{std::move(provider)}, m_module{std::move(module)} +{ +} + +key_management::ProviderKeyHandle Pkcs11KeyStore::Register( + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + const std::string& algorithm, + std::size_t key_size, + score::mw::crypto::KeyOperationPermission permissions) noexcept +{ + std::lock_guard lock(m_map_mutex); + const uint64_t opaque_id = m_next_opaque_id++; + SessionKey sk{}; + sk.session = session; + sk.object = object; + sk.is_token_object = false; + sk.op_mutex = std::make_shared(); + m_keys[opaque_id] = std::move(sk); + return key_management::ProviderKeyHandle{ + .opaque_id = opaque_id, + .provider_id = m_provider.lock() ? m_provider.lock()->GetProviderId() : common::kInvalidProviderId, + .permissions = permissions, + .algorithm = algorithm, + .key_size = key_size, + }; +} + +key_management::ProviderKeyHandle Pkcs11KeyStore::RegisterTokenObject(const SearchTemplate& search_template, + const std::string& algorithm, + std::size_t key_size) noexcept +{ + std::lock_guard lock(m_map_mutex); + const uint64_t opaque_id = m_next_opaque_id++; + SessionKey sk{}; + sk.is_token_object = true; + sk.token_search = search_template; + m_keys[opaque_id] = std::move(sk); + return key_management::ProviderKeyHandle{ + .opaque_id = opaque_id, + .provider_id = m_provider.lock() ? m_provider.lock()->GetProviderId() : common::kInvalidProviderId, + .permissions = score::mw::crypto::KeyOperationPermission::kNone, + .algorithm = algorithm, + .key_size = key_size, + }; +} + +Pkcs11KeyStore::ResolvedKey Pkcs11KeyStore::ResolveObject(uint64_t opaque_id, + CK_SESSION_HANDLE handler_session) noexcept +{ + // For session objects: try to acquire the per-key mutex (non-blocking). + // Returns a contended ResolvedKey if another handler already holds the lock; + // the caller surfaces kResourceBusy immediately.\n // + // For token objects: run C_FindObjects on handler_session (outside m_map_mutex + // to avoid holding it during a potentially blocking HSM call). + std::shared_ptr op_mtx; + bool is_token = false; + CK_SESSION_HANDLE creating_session = CK_INVALID_HANDLE; + CK_OBJECT_HANDLE stored_object = CK_INVALID_HANDLE; + SearchTemplate tmpl; + + { + std::lock_guard lock(m_map_mutex); + const auto it = m_keys.find(opaque_id); + if (it == m_keys.end()) + { + return {}; + } + is_token = it->second.is_token_object; + if (!is_token) + { + creating_session = it->second.session; + stored_object = it->second.object; + op_mtx = it->second.op_mutex; + } + else + { + tmpl = it->second.token_search; + } + } + + if (!is_token) + { + // Try to acquire the per-key mutex without blocking. If another handler + // is already using this session key, return a contended sentinel so the + // caller can immediately surface kResourceBusy instead of deadlocking. + std::unique_lock key_lock{*op_mtx, std::try_to_lock}; + if (!key_lock.owns_lock()) + { + ResolvedKey contended{}; + contended.contended = true; + return contended; + } + ResolvedKey resolved{}; + resolved.session = creating_session; + resolved.object = stored_object; + resolved.lock = std::move(key_lock); + return resolved; + } + + // --- token object path --- + if (handler_session == CK_INVALID_HANDLE) + { + return {}; + } + auto module = m_module.lock(); + if (!module) + { + return {}; + } + CK_FUNCTION_LIST* fns = module->GetFunctionList(); + if (fns == nullptr) + { + return {}; + } + + // Run C_FindObjects on the handler's session to obtain a session-local handle. + CK_ATTRIBUTE attrs_storage[3]; + CK_ULONG attr_count = 0U; + attrs_storage[attr_count++] = {CKA_CLASS, &tmpl.obj_class, sizeof(CK_OBJECT_CLASS)}; + if (!tmpl.label.empty()) + { + // MISRA C++:2023 Rule 8.2.3 deviation — PKCS#11 C API requires non-const pValue. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + attrs_storage[attr_count++] = { + CKA_LABEL, const_cast(tmpl.label.data()), static_cast(tmpl.label.size())}; + } + if (!tmpl.id.empty()) + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + attrs_storage[attr_count++] = { + CKA_ID, const_cast(tmpl.id.data()), static_cast(tmpl.id.size())}; + } + + const CK_RV rv_init = fns->C_FindObjectsInit(handler_session, attrs_storage, attr_count); + if (rv_init != CKR_OK) + { + std::cerr << LOG_PREFIX << "ResolveObject: C_FindObjectsInit failed: rv=" << static_cast(rv_init) + << '\n'; + return {}; + } + + CK_OBJECT_HANDLE found = CK_INVALID_HANDLE; + CK_ULONG count = 0U; + const CK_RV rv_find = fns->C_FindObjects(handler_session, &found, 1U, &count); + static_cast(fns->C_FindObjectsFinal(handler_session)); + + if ((rv_find != CKR_OK) || (count == 0U) || (found == CK_INVALID_HANDLE)) + { + std::cerr << LOG_PREFIX << "ResolveObject: token object not found on handler session\n"; + return {}; + } + + ResolvedKey resolved{}; + resolved.session = handler_session; + resolved.object = found; + return resolved; +} + +std::pair Pkcs11KeyStore::Lookup(uint64_t opaque_id) const noexcept +{ + std::lock_guard lock(m_map_mutex); + const auto it = m_keys.find(opaque_id); + if (it == m_keys.end()) + { + return {CK_INVALID_HANDLE, CK_INVALID_HANDLE}; + } + return {it->second.session, it->second.object}; +} + +score::crypto::Expected Pkcs11KeyStore::Release( + uint64_t opaque_id, + const key_management::ProviderKeyHandle& key) noexcept +{ + (void)key; // Unused for now; kept for future extensibility + + std::lock_guard lock(m_map_mutex); + + const auto it = m_keys.find(opaque_id); + if (it == m_keys.end()) + { + std::cerr << LOG_PREFIX << "Release: opaque_id=" << opaque_id << " not found\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidResourceId); + } + + const SessionKey& sk = it->second; + + if (!sk.is_token_object) + { + // Session object (generated/imported key): destroy the PKCS#11 object + // and return the ReadWrite session to the provider pool. + auto module_sptr = m_module.lock(); + auto provider_sptr = m_provider.lock(); + if (module_sptr) + { + CK_FUNCTION_LIST* fns = module_sptr->GetFunctionList(); + if ((fns != nullptr) && (sk.session != CK_INVALID_HANDLE) && (sk.object != CK_INVALID_HANDLE)) + { + const CK_RV rv = fns->C_DestroyObject(sk.session, sk.object); + if (rv != CKR_OK) + { + std::cerr << LOG_PREFIX << "C_DestroyObject failed: rv=" << static_cast(rv) << '\n'; + } + } + } + if ((sk.session != CK_INVALID_HANDLE) && provider_sptr) + { + const Pkcs11HandlerRequirements reqs{Pkcs11SessionType::ReadWrite, Pkcs11TokenAuthState::User}; + provider_sptr->ReleaseSession(sk.session, reqs); + } + } + // Token objects: the HSM object persists on the token — nothing to destroy + // and no session to release. + + m_keys.erase(it); + return std::monostate{}; +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp new file mode 100644 index 0000000..8b3eacc --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp @@ -0,0 +1,197 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_KEY_MANAGEMENT_PKCS11_KEY_STORE_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_KEY_MANAGEMENT_PKCS11_KEY_STORE_HPP + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_types.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "score/crypto/common/types.hpp" + +namespace score::crypto::daemon::provider::pkcs11 +{ + +class Pkcs11Provider; +class Pkcs11Module; + +/// Attributes used to locate a token-object key via C_FindObjects. +/// +/// Stored at registration time and used by ResolveObject() to find the key +/// on any handler session on demand. +struct SearchTemplate +{ + std::string label; ///< CKA_LABEL value (may be empty) + std::vector id; ///< CKA_ID bytes (may be empty) + CK_OBJECT_CLASS obj_class{CKO_SECRET_KEY}; ///< CKA_CLASS +}; + +/// Runtime table mapping opaque key IDs to PKCS#11 session and object handles. +/// +/// Pkcs11KeyStore owns the per-provider key state: each daemon-visible key ID +/// (uint64_t opaque_id) maps to a CK_OBJECT_HANDLE. Session objects additionally +/// carry the owning CK_SESSION_HANDLE (kept open to prevent SoftHSM2 from +/// destroying the object). Token objects carry only a SearchTemplate so that +/// any number of handlers can independently resolve a session-local handle +/// via ResolveObject(). +/// +/// ### Key lifecycle — session objects (GenerateKey / ImportKey) +/// - Register: opaque_id ↠session + object handle (creating session kept open) +/// - ResolveObject: returns the creating session + object handle + exclusive mutex lock. +/// The lock serializes concurrent use: only one handler may hold the +/// creating session at a time. Per PKCS#11 §4.10 a session object's +/// handle is only valid in the session that created it. +/// - Release: C_DestroyObject + return session to RW pool + erase entry +/// +/// ### Key lifecycle — token objects (LoadKey) +/// - RegisterTokenObject: opaque_id ↠SearchTemplate +/// - ResolveObject: runs C_FindObjects on the caller's session → session-local handle; +/// no lock is returned (token handles are session-independent) +/// - Release: erase map entry only (HSM object remains on token) +/// +/// ### Thread safety +/// All public methods protect m_keys with m_map_mutex (std::lock_guard). +class Pkcs11KeyStore +{ + public: + /// Constructor. + /// + /// \param provider weak_ptr to the parent Pkcs11Provider (for session management) + /// \param module weak_ptr to the Pkcs11Module (for GetFunctionList access) + Pkcs11KeyStore(std::weak_ptr provider, std::weak_ptr module); + + ~Pkcs11KeyStore() = default; + + Pkcs11KeyStore(const Pkcs11KeyStore&) = delete; + Pkcs11KeyStore& operator=(const Pkcs11KeyStore&) = delete; + Pkcs11KeyStore(Pkcs11KeyStore&&) = delete; + Pkcs11KeyStore& operator=(Pkcs11KeyStore&&) = delete; + + /// Register a session key (session + object for a newly generated or imported key). + /// + /// Called by Pkcs11KeyFactory::GenerateKey and Pkcs11KeyFactory::ImportKey. + /// Assigns a new opaque_id and stores the session/object pair. + [[nodiscard]] key_management::ProviderKeyHandle Register( + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + const std::string& algorithm, + std::size_t key_size, + score::mw::crypto::KeyOperationPermission permissions = + score::mw::crypto::KeyOperationPermission::kNone) noexcept; + + /// Register a persistent token object by storing its search template. + /// + /// Called by Pkcs11KeySlotHandler::LoadKey after C_FindObjects and + /// C_GetAttributeValue succeed. No session is stored: the caller releases the + /// find session back to the pool immediately. Future access uses ResolveObject() + /// which re-runs C_FindObjects on the calling handler's session. + [[nodiscard]] key_management::ProviderKeyHandle RegisterTokenObject(const SearchTemplate& search_template, + const std::string& algorithm, + std::size_t key_size) noexcept; + + /// Result of resolving a PKCS#11 key for use in a crypto operation. + /// + /// For session-object keys: `session` is the **creating** session (the only + /// session on which the handle is valid per PKCS#11 §4.10); `lock` is held + /// exclusively until the caller drops this struct, serializing concurrent + /// handlers that reference the same session key. + /// + /// For token-object keys: `session` is the handler's own session (from the + /// pool); `lock` is empty because token handles are valid on any session. + struct ResolvedKey + { + CK_SESSION_HANDLE session{CK_INVALID_HANDLE}; + CK_OBJECT_HANDLE object{CK_INVALID_HANDLE}; + /// Non-empty only for session objects — holds m_op_mutex exclusively. + std::optional> lock; + /// True when the key was found but its mutex could not be acquired because + /// another handler is already using it (try_lock failed). Callers should + /// return kResourceBusy rather than kInvalidArgument in this case. + bool contended{false}; + + [[nodiscard]] bool IsValid() const noexcept + { + return object != CK_INVALID_HANDLE && session != CK_INVALID_HANDLE; + } + }; + + /// Resolve a PKCS#11 key for use on a crypto handler session. + /// + /// For session-object keys (GenerateKey / ImportKey): returns the creating + /// session + stored object handle + an exclusive lock on the key's mutex. + /// The lock is held until the returned ResolvedKey is destroyed, ensuring + /// only one handler at a time drives the creating session. + /// + /// For token-object keys (LoadKey): re-runs C_FindObjects on handler_session + /// using the stored SearchTemplate, returning a session-local handle with no + /// lock. Multiple handlers may resolve the same token key concurrently. + /// + /// Returns an invalid ResolvedKey if opaque_id is not found, handler_session + /// is invalid (for token keys), the module is gone, or C_FindObjects finds nothing. + [[nodiscard]] ResolvedKey ResolveObject(uint64_t opaque_id, CK_SESSION_HANDLE handler_session) noexcept; + + /// Look up the (session, object_handle) pair for a daemon-opaque key ID. + /// + /// For token objects the session field is CK_INVALID_HANDLE — use + /// ResolveObject() when a live object handle is needed by a handler. + [[nodiscard]] std::pair Lookup(uint64_t opaque_id) const noexcept; + + /// Destroy a PKCS#11 session key via C_DestroyObject and erase from map. + /// + /// Called by Pkcs11KeyHandler::Release. For persistent token objects only + /// the map entry is erased; the HSM key object remains on the token. + /// + /// \param opaque_id The key ID to release + /// \param key The ProviderKeyHandle from the dying key handler (unused for now, + /// kept for future extensibility) + [[nodiscard]] score::crypto::Expected Release( + uint64_t opaque_id, + const key_management::ProviderKeyHandle& key) noexcept; + + private: + struct SessionKey + { + CK_SESSION_HANDLE session{CK_INVALID_HANDLE}; + CK_OBJECT_HANDLE object{CK_INVALID_HANDLE}; + /// True for persistent token objects; Release does not call C_DestroyObject. + bool is_token_object{false}; + /// Populated for token objects; used by ResolveObject() to locate + /// the key via C_FindObjects on an arbitrary handler session. + SearchTemplate token_search; + /// Serializes concurrent handler access to the creating session for session + /// objects. shared_ptr so the mutex is stable even if the map rehashes. + /// Null for token objects (no serialization needed). + std::shared_ptr op_mutex; + }; + + std::weak_ptr m_provider; + std::weak_ptr m_module; ///< for C_DestroyObject on release + mutable std::mutex m_map_mutex; + std::unordered_map m_keys; ///< opaque_id -> session+object + uint64_t m_next_opaque_id{1U}; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_KEY_MANAGEMENT_PKCS11_KEY_STORE_HPP diff --git a/score/crypto/daemon/provider/pkcs11/operations/factory/pkcs11_handler_factory.cpp b/score/crypto/daemon/provider/pkcs11/operations/factory/pkcs11_handler_factory.cpp new file mode 100644 index 0000000..039c859 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/factory/pkcs11_handler_factory.cpp @@ -0,0 +1,139 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/pkcs11/operations/factory/pkcs11_handler_factory.hpp" + +// Full Pkcs11Provider definition required here (forward-declared in the header) +// to call AcquireSession / ReleaseSession. +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_session_guard.hpp" + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/provider/executors/key_mgmt_executor.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_handler.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_handler.hpp" + +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +Pkcs11HandlerFactory::Pkcs11HandlerFactory(const Pkcs11Module& module, Pkcs11Provider& provider) noexcept + : m_module{module}, m_provider{provider} +{ +} + +score::Result Pkcs11HandlerFactory::CreateHandler(const common::HandlerId& handlerId, + const common::AlgorithmId& algorithm) +{ + if (handlerId == kHashHandlerId) + { + return CreateHashHandler(algorithm); + } + + if (handlerId == kMacHandlerId) + { + return CreateMacHandler(algorithm); + } + + if (handlerId == kKeyManagementHandlerId) + { + return CreateKeyManagementHandler(); + } + + const score::result::Error error( + static_cast(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation), + score::mw::crypto::kCryptoErrorDomain, + "Handler not supported by PKCS#11 provider: " + handlerId); + return score::Result(score::unexpect, error); +} + +score::Result Pkcs11HandlerFactory::CreateHashHandler(const common::AlgorithmId& algorithm) +{ + if (!Pkcs11HashHandler::IsAlgorithmSupported(algorithm)) + { + const score::result::Error error(static_cast( + score::crypto::daemon::common::DaemonErrorCode::kUnsupportedAlgorithm), + score::mw::crypto::kCryptoErrorDomain, + "Algorithm not supported for PKCS#11 hash handler: " + algorithm); + return score::Result(score::unexpect, error); + } + + std::cout << "[PKCS11_HANDLER_FACTORY] Creating HASH handler for algorithm: " << algorithm << "\n"; + + Pkcs11SessionGuard guard(m_provider, Pkcs11HashHandler::kRequirements); + if (!guard) + { + const score::result::Error error(static_cast(guard.error()), + score::mw::crypto::kCryptoErrorDomain, + "PKCS#11: failed to acquire session for handler"); + return score::Result(score::unexpect, error); + } + + auto executor = std::make_unique(m_module); + auto handler = std::make_shared(std::move(executor), guard.get(), algorithm, &m_provider); + static_cast(guard.release()); + return handler; +} + +score::Result Pkcs11HandlerFactory::CreateMacHandler(const common::AlgorithmId& algorithm) +{ + if (!Pkcs11MacHandler::IsAlgorithmSupported(algorithm)) + { + const score::result::Error error(static_cast( + score::crypto::daemon::common::DaemonErrorCode::kUnsupportedAlgorithm), + score::mw::crypto::kCryptoErrorDomain, + "Algorithm not supported for PKCS#11 MAC handler: " + algorithm); + return score::Result(score::unexpect, error); + } + + Pkcs11SessionGuard guard(m_provider, Pkcs11MacHandler::kRequirements); + if (!guard) + { + const score::result::Error error(static_cast(guard.error()), + score::mw::crypto::kCryptoErrorDomain, + "PKCS#11: failed to acquire session for MAC handler"); + return score::Result(score::unexpect, error); + } + + auto mac_executor = std::make_unique(m_module); + auto handler = std::shared_ptr( + new Pkcs11MacHandler(std::move(mac_executor), m_module, guard.get(), algorithm, &m_provider)); + static_cast(guard.release()); + return handler; +} + +score::Result Pkcs11HandlerFactory::CreateKeyManagementHandler() +{ + auto km_service = m_provider.GetKeyManagementService(); + if (!km_service) + { + const score::result::Error error( + static_cast(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument), + score::mw::crypto::kCryptoErrorDomain, + "PKCS#11 key management handler requires KeyManagementService"); + return score::Result(score::unexpect, error); + } + + auto slot_handler = m_provider.GetKeySlotHandler(key_management::KeySlotConfig{}); + auto key_factory = m_provider.GetKeyFactory(); + auto executor = std::make_unique(key_factory, slot_handler, km_service); + auto km_handler = std::make_shared( + m_provider.shared_from_this(), std::move(executor), m_provider.GetProviderId()); + return km_handler; +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/operations/factory/pkcs11_handler_factory.hpp b/score/crypto/daemon/provider/pkcs11/operations/factory/pkcs11_handler_factory.hpp new file mode 100644 index 0000000..e41fcf9 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/factory/pkcs11_handler_factory.hpp @@ -0,0 +1,75 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_OPERATIONS_FACTORY_HANDLER_FACTORY_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_OPERATIONS_FACTORY_HANDLER_FACTORY_HPP + +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/key_management/core/key_management_service.hpp" +#include "score/crypto/daemon/provider/handler/i_crypto_handler_factory.hpp" +#include "score/crypto/daemon/provider/handler/i_handler.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" +#include "score/result/result.h" + +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +// Forward declaration to avoid circular dependency: +// pkcs11_provider.hpp includes pkcs11_handler_factory.hpp, so we cannot +// include pkcs11_provider.hpp here. The full definition is available in the .cpp. +class Pkcs11Provider; + +/// @brief Predefined handler IDs supported by the PKCS#11 factory. +inline constexpr const char* const kHashHandlerId = "HASH"; +inline constexpr const char* const kMacHandlerId = "MAC"; +inline constexpr const char* const kKeyManagementHandlerId = "KEY_MANAGEMENT"; + +/// @brief Factory that creates PKCS#11-backed crypto handlers. +/// +/// Each factory instance is associated with a single PKCS#11 module and a +/// reference to the parent Pkcs11Provider. Handlers acquire their own session +/// from the provider pool at construction and return it on destruction. +class Pkcs11HandlerFactory final : public handler::ICryptoHandlerFactory +{ + public: + /// @param module Reference to the initialised Pkcs11Module (non-owning). + /// @param provider Reference to the owning Pkcs11Provider for session acquisition (non-owning). + /// The provider is also used to obtain KeyManagementService at handler creation time. + Pkcs11HandlerFactory(const Pkcs11Module& module, Pkcs11Provider& provider) noexcept; + ~Pkcs11HandlerFactory() override = default; + + Pkcs11HandlerFactory(const Pkcs11HandlerFactory&) = delete; + Pkcs11HandlerFactory& operator=(const Pkcs11HandlerFactory&) = delete; + Pkcs11HandlerFactory(Pkcs11HandlerFactory&&) = delete; + Pkcs11HandlerFactory& operator=(Pkcs11HandlerFactory&&) = delete; + + /// @brief Create a handler for the requested handler/algorithm combination. + /// + /// Acquires a session from the provider pool using the handler type's + /// static kRequirements. The session is released on handler destruction. + [[nodiscard]] score::Result CreateHandler(const common::HandlerId& handlerId, + const common::AlgorithmId& algorithm) override; + + private: + [[nodiscard]] score::Result CreateHashHandler(const common::AlgorithmId& algorithm); + [[nodiscard]] score::Result CreateMacHandler(const common::AlgorithmId& algorithm); + [[nodiscard]] score::Result CreateKeyManagementHandler(); + + const Pkcs11Module& m_module; + Pkcs11Provider& m_provider; ///< non-owning; provider always outlives the factory +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_OPERATIONS_FACTORY_HANDLER_FACTORY_HPP diff --git a/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_context.hpp b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_context.hpp new file mode 100644 index 0000000..c38f540 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_context.hpp @@ -0,0 +1,39 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_HASH_CONTEXT_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_HASH_CONTEXT_HPP + +#include +#include + +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +/// Groups the stable per-context parameters for Pkcs11HashExecutor operations. +/// +/// These values are established once during handler construction / InitializeContext() +/// and remain constant for the lifetime of the handler context. Passing them as a +/// struct reduces the executor's Execute() parameter count, improving readability +/// and MISRA C++ Rule 8-0-1 compliance. +struct Pkcs11HashExecutionContext +{ + CK_SESSION_HANDLE session{CK_INVALID_HANDLE}; + CK_MECHANISM mechanism{}; + std::size_t digest_size{0U}; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_HASH_CONTEXT_HPP diff --git a/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.cpp b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.cpp new file mode 100644 index 0000000..8bcd2fd --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.cpp @@ -0,0 +1,288 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.hpp" +#include "score/crypto/daemon/provider/handler/operations/hash_handler_operations.hpp" +#include "score/crypto/daemon/provider/handler/src/handler_utils.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +using common::RequestParameters; +using common::ResponseParameters; +using common::StreamOperationState; +using score::crypto::daemon::common::DaemonErrorCode; + +Pkcs11HashExecutor::Pkcs11HashExecutor(const Pkcs11Module& module) noexcept + : m_module{module}, + m_functionList{module.GetFunctionList()}, + m_supportsMessageDigest{module.GetCapabilities().supportsMessageDigest} +{ +} + +bool Pkcs11HashExecutor::SupportsMessageDigest() const noexcept +{ + return m_supportsMessageDigest; +} + +// static +Expected Pkcs11HashExecutor::ValidateStreamTransition( + const common::OperationAction action, + const StreamOperationState currentState, + StreamOperationState& nextState) noexcept +{ + namespace ops = handler::hash_handler_operations; + std::string_view opId; + if (action == ops::HASH_INIT) + { + opId = "START"; + } + else if (action == ops::HASH_UPDATE) + { + opId = "UPDATE"; + } + else if (action == ops::HASH_FINISH) + { + opId = "FINISH"; + } + else + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidOperation); + } + return handler::handler_utils::ValidateStreamOperationSequence(currentState, opId, nextState); +} + +Expected Pkcs11HashExecutor::Execute( + Pkcs11HashExecutionContext& ctx, + const common::OperationAction operationAction, + RequestParameters& request, + const StreamOperationState currentState, + StreamOperationState& nextState) noexcept +{ + namespace ops = handler::hash_handler_operations; + + // --- Single-shot: no stream state transition needed --- + if (operationAction == ops::HASH_SS) + { + if (currentState != StreamOperationState::IDLE) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kOperationInProgress); + } + nextState = StreamOperationState::IDLE; + return ExecuteDigestSingleShot(ctx.session, ctx.mechanism, request); + } + + // Reset operation: HASH_RESET + if (operationAction == ops::HASH_RESET) + { + // TODO: Is this correct for the reset? + Abort(ctx.session); + // Reset(); + return {}; + } + + // --- GET_DIGEST_SIZE: handled without PKCS#11 calls --- + if (operationAction == ops::HASH_GET_DIGEST_SIZE) + { + // Digest size is algorithm-dependent; handler resolves this before calling executor. + // If we reach here, it means the handler delegates — return unsupported to let handler handle it. + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); + } + + // --- Streaming operations: validate state transition --- + const auto sequenceResult = ValidateStreamTransition(operationAction, currentState, nextState); + if (!sequenceResult.has_value()) + { + return make_unexpected(sequenceResult.error()); + } + + // --- Dispatch to PKCS#11 call --- + if (operationAction == ops::HASH_FINISH) + { + auto result = ExecuteDigestFinal(ctx.session, request); + if (!result.has_value()) + { + nextState = currentState; + } + return result; + } + + const auto result = [&]() -> Expected { + if (operationAction == ops::HASH_INIT) + return ExecuteDigestInit(ctx.session, ctx.mechanism); + if (operationAction == ops::HASH_UPDATE) + return ExecuteDigestUpdate(ctx.session, request); + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidOperation); + }(); + + // Revert state on failure (caller should not advance) + if (!result.has_value()) + { + nextState = currentState; + } + + return {}; +} + +// ============================================================================ +// Private: PKCS#11 Digest Operations +// ============================================================================ + +Expected Pkcs11HashExecutor::ExecuteDigestInit( + const CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism) noexcept +{ + const CK_RV rv = m_functionList->C_DigestInit(session, &mechanism); + if (rv != CKR_OK) + { + return make_unexpected(Pkcs11Module::MapErrorReturn(rv)); + } + return std::monostate{}; +} + +Expected Pkcs11HashExecutor::ExecuteDigestUpdate( + const CK_SESSION_HANDLE session, + RequestParameters& request) noexcept +{ + if (request.empty()) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientParameters); + } + + auto* bufIn = std::get_if(&request[0]); + if (!bufIn || bufIn->data == nullptr || bufIn->size == 0) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + + // MISRA C++:2023 Rule 8.2.3 deviation — PKCS#11 C API (C_DigestUpdate) requires non-const pPart. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto* data = const_cast(static_cast(bufIn->data)); + const CK_RV rv = m_functionList->C_DigestUpdate(session, data, static_cast(bufIn->size)); + if (rv != CKR_OK) + { + return make_unexpected(Pkcs11Module::MapErrorReturn(rv)); + } + return std::monostate{}; +} + +Expected Pkcs11HashExecutor::ExecuteDigestFinal( + const CK_SESSION_HANDLE session, + RequestParameters& request) noexcept +{ + // For PKCS11, the output buffer comes from the handler's internal buffer + // passed via parameters + if (request.empty()) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientParameters); + } + + auto* bufInOut = std::get_if(&request[0]); + if (!bufInOut || bufInOut->data == nullptr || bufInOut->size == 0) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + + // TODO: The OpenSSL HashHandler as well as the general HashHandler operation does support an additional + // final data chunk. Not sure if this shall / can be supported for PKCS#11 + + auto digestLen = static_cast(bufInOut->size); + const CK_RV rv = m_functionList->C_DigestFinal(session, bufInOut->data, &digestLen); + if (rv != CKR_OK) + { + return make_unexpected(Pkcs11Module::MapErrorReturn(rv)); + } + + // Update size to actual digest length and add to output + ResponseParameters response; + response.push_back(common::VirtualMemoryBufferConst{bufInOut->data, static_cast(digestLen)}); + return response; +} + +Expected +Pkcs11HashExecutor::ExecuteDigestSingleShot(const CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism, + RequestParameters& request) noexcept +{ + if (request.empty()) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientParameters); + } + + // Extract input buffer + auto* inputBuf = std::get_if(&request[0]); + if (!inputBuf || inputBuf->data == nullptr || inputBuf->size == 0) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + + // Extract output buffer parameters + auto* outputBuf = std::get_if(&request[1]); + if (!outputBuf || outputBuf->data == nullptr || outputBuf->size == 0) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + + // For PKCS#11 v2.40: C_DigestInit + C_Digest (two-step single-shot) + // For PKCS#11 v3.0+: C_MessageDigestInit + C_MessageDigest could be used + // when m_supportsMessageDigest is true, avoiding the active-operation + // slot on the session. Currently not dispatched because SoftHSM is v2.40. + // When a v3.0 token is available, add: + // if (m_supportsMessageDigest) { return ExecuteMessageDigest(session, mechanism, config); } + + // Defensively abort any leftover operation to ensure session is clean + // (in case a previous operation on this session was not properly finalised). + // Dispatch through the function list — not via direct C-linkage — so that + // the call correctly targets the library that owns this session. + Abort(session); + + const CK_RV initRv = m_functionList->C_DigestInit(session, &mechanism); + if (initRv != CKR_OK) + { + return make_unexpected(Pkcs11Module::MapErrorReturn(initRv)); + } + + auto digestLen = static_cast(outputBuf->size); + // MISRA C++:2023 Rule 8.2.3 deviation — PKCS#11 C API (C_Digest) requires non-const pData. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto* inputData = const_cast(static_cast(inputBuf->data)); + const CK_RV digestRv = m_functionList->C_Digest( + session, inputData, static_cast(inputBuf->size), outputBuf->data, &digestLen); + if (digestRv != CKR_OK) + { + return make_unexpected(Pkcs11Module::MapErrorReturn(digestRv)); + } + + // Update size to actual digest length and add to output + ResponseParameters response; + response.push_back(common::VirtualMemoryBufferConst{outputBuf->data, static_cast(digestLen)}); + return response; +} + +void Pkcs11HashExecutor::Abort(const CK_SESSION_HANDLE session) noexcept +{ + // Call C_DigestFinal with a dummy buffer to abort any active digest operation + // and return the session to IDLE state. Errors are intentionally ignored: + // if no operation is active, C_DigestFinal returns CKR_OPERATION_NOT_INITIALIZED + // which is harmless here. + // Dispatch through the stored function list — never through a direct C-linkage + // symbol — so that the call correctly targets the library that owns this session. + std::uint8_t dummyBuf[64U]{0U}; // NOLINT(cppcoreguidelines-pro-bounds-array-init) + CK_ULONG dummyLen = sizeof(dummyBuf); + static_cast(m_functionList->C_DigestFinal(session, dummyBuf, &dummyLen)); +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.hpp b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.hpp new file mode 100644 index 0000000..1f3161a --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.hpp @@ -0,0 +1,114 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_HASH_EXECUTOR_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_HASH_EXECUTOR_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_context.hpp" + +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +class Pkcs11Module; +struct Pkcs11Capabilities; + +/// @brief Executor (visitor) that translates generic operation IDs to PKCS#11 C_Digest* calls. +/// +/// Owns no session state — receives the session handle and mechanism from the handler. +/// Reuses handler_utils::ValidateStreamOperationSequence for stream state management. +/// +/// Version-aware dispatch: +/// - v2.40 (SoftHSM): uses C_DigestInit + C_Digest for single-shot +/// - v3.0+: uses C_MessageDigestInit + C_MessageDigest (when supportsMessageDigest is true) +/// This avoids occupying the session's active-operation slot for single-shot digests. +class Pkcs11HashExecutor final +{ + public: + /// @brief Construct executor with reference to the PKCS#11 module. + /// @param module Non-owning reference to the initialised Pkcs11Module. + /// Capabilities are cached from the module at construction time. + explicit Pkcs11HashExecutor(const Pkcs11Module& module) noexcept; + + ~Pkcs11HashExecutor() = default; + + Pkcs11HashExecutor(const Pkcs11HashExecutor&) = delete; + Pkcs11HashExecutor& operator=(const Pkcs11HashExecutor&) = delete; + Pkcs11HashExecutor(Pkcs11HashExecutor&&) noexcept = default; + Pkcs11HashExecutor& operator=(Pkcs11HashExecutor&&) noexcept = default; + + // TODO: Consider reducing the number of parameters + /// @brief Dispatch the operation to the corresponding C_Digest* call. + /// @param ctx Stable per-context parameters (session, mechanism, digest_size). + /// @param operationAction The operation action (e.g., HASH_INIT, HASH_UPDATE, HASH_FINISH, HASH_SS). + /// @param request RequestParameters with parameters. + /// @param currentState Current streaming state (read/write). + /// @param nextState Output: next streaming state on success. + [[nodiscard]] Expected Execute( + Pkcs11HashExecutionContext& ctx, + common::OperationAction operationAction, + common::RequestParameters& request, + common::StreamOperationState currentState, + common::StreamOperationState& nextState) noexcept; + + /// @brief Abort any active digest operation on the session by calling C_DigestFinal + /// with a dummy buffer, returning the session to IDLE state. + /// + /// All dispatch goes through the function list cached at construction — not + /// through direct C-linkage symbols — so this correctly targets the library + /// that owns the session. + /// @note Safe to call when the session has no active operation (error is ignored). + void Abort(CK_SESSION_HANDLE session) noexcept; + + /// @brief Query whether the underlying token supports v3.0 message-based digest. + [[nodiscard]] bool SupportsMessageDigest() const noexcept; + + private: + /// @brief Validate a streaming operation action against the current state and compute the next + /// state in one step, eliminating the intermediate string representation from call sites. + /// + /// Returns an error if the action is not a recognised streaming operation or if the + /// current state does not permit the transition. + [[nodiscard]] static Expected + ValidateStreamTransition(common::OperationAction action, + common::StreamOperationState currentState, + common::StreamOperationState& nextState) noexcept; + + [[nodiscard]] Expected ExecuteDigestInit( + CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism) noexcept; + + [[nodiscard]] Expected ExecuteDigestUpdate( + CK_SESSION_HANDLE session, + common::RequestParameters& request) noexcept; + + [[nodiscard]] Expected + ExecuteDigestFinal(CK_SESSION_HANDLE session, common::RequestParameters& request) noexcept; + + [[nodiscard]] Expected + ExecuteDigestSingleShot(CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism, + common::RequestParameters& request) noexcept; + + const Pkcs11Module& m_module; + CK_FUNCTION_LIST* m_functionList; ///< cached at construction from m_module.GetFunctionList() + bool m_supportsMessageDigest; ///< cached from Pkcs11Capabilities at construction +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_HASH_EXECUTOR_HPP diff --git a/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_handler.cpp b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_handler.cpp new file mode 100644 index 0000000..4c57f1e --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_handler.cpp @@ -0,0 +1,221 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +// Full Pkcs11Provider definition required for ReleaseSession call in destructor. +#include "score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_handler.hpp" +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/algorithm_info.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/handler/operations/hash_handler_operations.hpp" +#include "score/crypto/daemon/provider/pkcs11/detail/pkcs11_algorithm_info.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp" + +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +using common::RequestParameters; +using common::ResponseParameters; +using common::StreamOperationState; +using score::crypto::daemon::common::DaemonErrorCode; + +// --- Supported algorithms (same set as OpenSSL HashHandler) --- +static constexpr const char* kSupportedAlgorithms[] = {"SHA256", "SHA384", "SHA512", "SHA224", "SHA1", "MD5"}; + +// --- Algorithm → CKM_* mapping --- + +CK_MECHANISM_TYPE Pkcs11HashHandler::MapAlgorithm(const std::string_view algorithm) noexcept +{ + return detail::LookupHashMechanism(algorithm); +} + +std::uint64_t Pkcs11HashHandler::GetDigestSize() const noexcept +{ + return static_cast( + score::crypto::daemon::common::LookupDigestSize(std::string_view{m_algorithm.data(), m_algorithm.size()}) + .value_or(64U)); // safe default (largest supported) +} + +// --- Construction / destruction --- + +Pkcs11HashHandler::Pkcs11HashHandler(std::unique_ptr executor, + const CK_SESSION_HANDLE session, + const common::AlgorithmId& algorithm, + Pkcs11Provider* provider) + : m_executor{std::move(executor)}, + m_ctx{}, + m_provider{provider}, + m_algorithm{algorithm}, + m_state{StreamOperationState::IDLE}, + m_outputBuffer{} +{ + m_ctx.session = session; + m_ctx.mechanism.mechanism = MapAlgorithm(m_algorithm); + m_ctx.mechanism.pParameter = nullptr; + m_ctx.mechanism.ulParameterLen = 0U; + m_ctx.digest_size = static_cast(GetDigestSize()); +} + +Pkcs11HashHandler::~Pkcs11HashHandler() +{ + // Abort any active PKCS#11 operation before returning the session to the pool. + // This ensures the session is in IDLE state when released for reuse by the next handler. + // If streaming was not completed (e.g. early destruction), C_DigestFinal is called + // with a dummy buffer to cleanly abort the operation state. + m_executor->Abort(m_ctx.session); + + // Return the dedicated session to the provider pool. + // Guard against nullptr provider (e.g. unit tests that mock without a provider). + if ((m_provider != nullptr) && (m_ctx.session != CK_INVALID_HANDLE)) + { + m_provider->ReleaseSession(m_ctx.session, kRequirements); + m_ctx.session = CK_INVALID_HANDLE; + } +} + +// --- Static algorithm check --- + +bool Pkcs11HashHandler::IsAlgorithmSupported(const common::AlgorithmId& algorithm) noexcept +{ + for (const char* supported : kSupportedAlgorithms) + { + if (algorithm == supported) + { + return true; + } + } + return false; +} + +// --- Handler interface: InitializeContext --- + +Expected Pkcs11HashHandler::InitializeContext( + const handler::InitializationParams& /*init_params*/) +{ + // Validate algorithm (m_algorithm is set at construction). + bool found{false}; + for (const char* supported : kSupportedAlgorithms) + { + if (m_algorithm == supported) + { + found = true; + break; + } + } + if (!found) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedAlgorithm); + } + + m_ctx.mechanism.mechanism = MapAlgorithm(m_algorithm); + m_ctx.mechanism.pParameter = nullptr; + m_ctx.mechanism.ulParameterLen = 0U; + m_ctx.digest_size = static_cast(GetDigestSize()); + m_state = StreamOperationState::IDLE; + m_outputBuffer.clear(); + + return std::monostate{}; +} + +// --- Handler interface: Execute --- + +Expected Pkcs11HashHandler::Execute( + const common::OperationIdentifier& operationId, + RequestParameters& request) +{ + namespace ops = handler::hash_handler_operations; + + // Validate session is still open before any PKCS#11 call. + if ((m_provider != nullptr) && !m_provider->ValidateSession(m_ctx.session)) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kSessionInvalid); + } + + // Handle GET_DIGEST_SIZE locally — no PKCS#11 call needed. + if (operationId.operationAction == ops::HASH_GET_DIGEST_SIZE) + { + ResponseParameters response; + response.push_back(GetDigestSize()); + return response; + } + + // All other operations delegated to the executor. + m_outputBuffer.clear(); + + // TODO: When mediator is refactored, this needs to be revisted + // Inject an internal output buffer for HASH_SS and HASH_FINISH when the + // caller has not supplied one. This mirrors the OpenSSL handler's behaviour + // of using an internal buffer and populating response.parameter on return. + auto allocate_out_buffer = false; + if ((operationId.operationAction == ops::HASH_SS) && request.size() < 2) + { + allocate_out_buffer = true; + } + else if ((operationId.operationAction == ops::HASH_FINISH) && request.empty()) + { + allocate_out_buffer = true; + } + if (allocate_out_buffer) + { + m_outputBuffer.assign(GetDigestSize(), 0U); + common::VirtualMemoryBuffer outputMapped{m_outputBuffer.data(), m_outputBuffer.size()}; + request.push_back(outputMapped); + } + + StreamOperationState nextState{m_state}; + const auto response = m_executor->Execute(m_ctx, operationId.operationAction, request, m_state, nextState); + + if (!response.has_value()) + { + return response; + } + + auto response_value = response.value(); + common::VirtualMemoryBufferConst* non_owning_buffer_ptr = nullptr; + if (!response_value.empty()) + { + non_owning_buffer_ptr = std::get_if(&response_value.back()); + } + + // TODO: Rework and harmonize who is responsible for: + // - Allocation of Buffer if not provided (Handler vs Executor) + // - Construction of Response including the correct type when a buffer is allocated + if (allocate_out_buffer && non_owning_buffer_ptr) + { + response_value.pop_back(); + response_value.push_back(common::OwnedBuffer{m_outputBuffer}); + } + + m_state = nextState; + + return response_value; +} + +// --- Handler interface: Reset --- + +Expected Pkcs11HashHandler::Reset() +{ + // Delegate the abort to the executor so that all PKCS#11 dispatch is + // centralised there and goes through the function list, not direct C-linkage. + if (m_state != StreamOperationState::IDLE) + { + m_executor->Abort(m_ctx.session); + } + + m_state = StreamOperationState::IDLE; + m_outputBuffer.clear(); + return std::monostate{}; +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_handler.hpp b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_handler.hpp new file mode 100644 index 0000000..e562822 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_handler.hpp @@ -0,0 +1,105 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_HASH_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_HASH_HANDLER_HPP + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/handler/i_handler.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_context.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" + +#include +#include + +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +// Forward declaration: full definition not needed in the header -- only a pointer +// is stored. pkcs11_hash_handler.cpp includes pkcs11_provider.hpp. +class Pkcs11Provider; + +/// @brief Handler for PKCS#11 hash operations. +/// +/// Each instance owns a dedicated PKCS#11 session acquired from the parent +/// Pkcs11Provider pool at construction and released on destruction. +/// This ensures fully independent concurrent streaming: two simultaneous +/// IHashContext operations each use their own session and never interfere. +/// +/// kRequirements declares the session type and auth state needed by this +/// handler type so the factory and provider pool can allocate correctly. +class Pkcs11HashHandler final : public handler::Handler +{ + public: + /// @brief Session and auth requirements for hash operations. + /// + /// Hash (digest) operations require no private key access -- a ReadOnly + /// Public session is sufficient. This is the least-privilege assignment. + static constexpr Pkcs11HandlerRequirements kRequirements{Pkcs11SessionType::ReadOnly, Pkcs11TokenAuthState::Public}; + + /// @brief Construct handler with executor, dedicated session, algorithm, and provider back-ref. + /// @param executor Owned executor for PKCS#11 API translation. + /// @param session Session handle acquired from the provider pool (non-owning reference). + /// @param algorithm Algorithm ID (e.g., "SHA256"). + /// @param provider Non-owning pointer to the parent provider for session release on destruction. + explicit Pkcs11HashHandler(std::unique_ptr executor, + CK_SESSION_HANDLE session, + const common::AlgorithmId& algorithm, + Pkcs11Provider* provider); + + /// @brief Destructor: returns the dedicated session to the provider pool. + ~Pkcs11HashHandler() override; + + Pkcs11HashHandler(const Pkcs11HashHandler&) = delete; + Pkcs11HashHandler& operator=(const Pkcs11HashHandler&) = delete; + Pkcs11HashHandler(Pkcs11HashHandler&&) = delete; + Pkcs11HashHandler& operator=(Pkcs11HashHandler&&) = delete; + + // --- Handler interface --- + [[nodiscard]] Expected InitializeContext( + const handler::InitializationParams& init_params) override; + + [[nodiscard]] Expected Execute( + const common::OperationIdentifier& operationId, + common::RequestParameters& request) override; + + [[nodiscard]] Expected Reset() override; + + /// @brief Returns a static handler configuration for PKCS#11 hash. + /// @brief Check if the given algorithm is supported by this handler. + [[nodiscard]] static bool IsAlgorithmSupported(const common::AlgorithmId& algorithm) noexcept; + + private: + /// @brief Map algorithm ID string to CK_MECHANISM_TYPE. + [[nodiscard]] static CK_MECHANISM_TYPE MapAlgorithm(std::string_view algorithm) noexcept; + + /// @brief Return the digest output size for the current algorithm. + [[nodiscard]] std::uint64_t GetDigestSize() const noexcept; + + std::unique_ptr m_executor; + Pkcs11HashExecutionContext m_ctx; ///< stable per-context parameters for executor + Pkcs11Provider* m_provider; ///< Non-owning; used for ReleaseSession on destruction + common::AlgorithmId m_algorithm; + common::StreamOperationState m_state; + std::vector m_outputBuffer; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_HASH_HANDLER_HPP diff --git a/score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.cpp b/score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.cpp new file mode 100644 index 0000000..033acc0 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.cpp @@ -0,0 +1,72 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.hpp" + +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp" + +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +Pkcs11KeyManagementHandler::Pkcs11KeyManagementHandler(std::shared_ptr provider, + std::unique_ptr executor, + common::ProviderId provider_id) + : m_provider{std::move(provider)}, m_executor{std::move(executor)}, m_ctx{provider_id, 0U, 0U} +{ +} + +Expected Pkcs11KeyManagementHandler::InitializeContext( + const handler::InitializationParams& init_params) +{ + m_ctx.client_id = init_params.client_id; + m_ctx.context_node_id = init_params.context_node_id; + if (init_params.provider_id != common::kInvalidProviderId) + { + m_ctx.provider_id = init_params.provider_id; + } + return std::monostate{}; +} + +Expected Pkcs11KeyManagementHandler::Reset() +{ + return std::monostate{}; +} + +Expected +Pkcs11KeyManagementHandler::Execute(const common::OperationIdentifier& operationId, common::RequestParameters& request) +{ + if (!m_executor) + { + std::cerr << LOG_PREFIX << "Execute: executor not injected\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + return m_executor->Execute(m_ctx, operationId, request); +} + +static constexpr const char* kSupportedAlgorithms[] = + {"HMAC-SHA256", "HMAC-SHA384", "HMAC-SHA512", "AES-128", "AES-192", "AES-256"}; + +bool Pkcs11KeyManagementHandler::IsAlgorithmSupported(const common::AlgorithmId& algorithm) noexcept +{ + for (const char* supported : kSupportedAlgorithms) + { + if (algorithm == supported) + { + return true; + } + } + return false; +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.hpp b/score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.hpp new file mode 100644 index 0000000..9fdbdf6 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.hpp @@ -0,0 +1,99 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_OPERATIONS_KEY_MANAGEMENT_PKCS11_KEY_MANAGEMENT_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_OPERATIONS_KEY_MANAGEMENT_PKCS11_KEY_MANAGEMENT_HANDLER_HPP + +#include "score/crypto/daemon/key_management/core/key_management_service.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_factory.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" +#include "score/crypto/daemon/provider/executors/key_mgmt_context.hpp" +#include "score/crypto/daemon/provider/executors/key_mgmt_executor.hpp" +#include "score/crypto/daemon/provider/handler/i_handler.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +class Pkcs11Provider; + +/// PKCS#11 key management handler implementing only the Handler dispatch interface. +/// +/// Implements the Handler dispatch interface (via Execute → KeyManagementExecutor). +/// The IKeyFactory implementation is delegated to a separate Pkcs11KeyFactory instance. +/// The opaque-id map (m_keys) is delegated to separate Pkcs11KeyStore instance. +/// +/// This stripped-down handler focuses solely on operation dispatch for key management +/// operations: KEY_GENERATE, KEY_LOAD, KEY_RELEASE, KEY_SLOT_INFO, etc. +/// +/// ### Thread safety +/// m_factory and m_slot_handler are immutable after construction (final pointers). +/// +/// ### Design +/// - Pkcs11KeyFactory (injected) : handles GenerateKey and ImportKey +/// - Pkcs11KeyStore : handles opaque-id map and key release +/// - Pkcs11KeyManagementHandler : dispatch only +class Pkcs11KeyManagementHandler final : public handler::Handler +{ + public: + Pkcs11KeyManagementHandler(std::shared_ptr provider, + std::unique_ptr executor, + common::ProviderId provider_id); + ~Pkcs11KeyManagementHandler() override = default; + + Pkcs11KeyManagementHandler(const Pkcs11KeyManagementHandler&) = delete; + Pkcs11KeyManagementHandler& operator=(const Pkcs11KeyManagementHandler&) = delete; + Pkcs11KeyManagementHandler(Pkcs11KeyManagementHandler&&) = delete; + Pkcs11KeyManagementHandler& operator=(Pkcs11KeyManagementHandler&&) = delete; + + // ------------------------------------------------------------------ + // Handler interface + // ------------------------------------------------------------------ + + /// No-op: all context is injected at construction. + [[nodiscard]] Expected InitializeContext( + const handler::InitializationParams& init_params) override; + + /// Dispatch key management operations via the shared executor. + /// + /// Handled: KEY_GENERATE, KEY_LOAD, KEY_RELEASE, KEY_SLOT_INFO, + /// KEY_WRAP, KEY_UNWRAP, KEY_DERIVE (stubs) + [[nodiscard]] Expected Execute( + const common::OperationIdentifier& operationId, + common::RequestParameters& request) override; + + [[nodiscard]] Expected Reset() override; + + /// @brief Check if the given algorithm is supported by this handler. + [[nodiscard]] static bool IsAlgorithmSupported(const common::AlgorithmId& algorithm) noexcept; + + private: + std::shared_ptr m_provider; + std::unique_ptr m_executor; + crypto_executor::KeyMgmtExecutionContext m_ctx; + + static constexpr std::string_view LOG_PREFIX = "[PKCS11_KEY_MGMT_HANDLER] "; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_OPERATIONS_KEY_MANAGEMENT_PKCS11_KEY_MANAGEMENT_HANDLER_HPP diff --git a/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_context.hpp b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_context.hpp new file mode 100644 index 0000000..19326c2 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_context.hpp @@ -0,0 +1,43 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_MAC_CONTEXT_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_MAC_CONTEXT_HPP + +#include "score/mw/crypto/api/common/types.hpp" // OperationMode + +#include +#include + +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +/// Groups the stable per-context parameters for Pkcs11MacExecutor operations. +/// +/// These values are established once during InitializeContext() and remain +/// constant for the lifetime of the handler context. Passing them as a +/// struct reduces the executor's Execute() call from 9 parameters to 5, +/// improving readability and MISRA C++ Rule 8-0-1 compliance. +struct Pkcs11MacExecutionContext +{ + CK_SESSION_HANDLE session{CK_INVALID_HANDLE}; + CK_MECHANISM mechanism{}; + CK_OBJECT_HANDLE key_object{CK_INVALID_HANDLE}; + std::size_t mac_size{0U}; + score::mw::crypto::OperationMode operation_mode{score::mw::crypto::OperationMode::kGenerate}; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_MAC_CONTEXT_HPP diff --git a/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.cpp b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.cpp new file mode 100644 index 0000000..e4ef7bf --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.cpp @@ -0,0 +1,635 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.hpp" +#include "score/crypto/daemon/provider/handler/operations/mac_handler_operations.hpp" +#include "score/crypto/daemon/provider/handler/src/handler_utils.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" +#include "score/mw/crypto/api/common/types.hpp" + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +using common::RequestParameters; +using common::ResponseParameters; +using common::StreamOperationState; +using score::crypto::daemon::common::DaemonErrorCode; + +static constexpr std::string_view LOG_PREFIX = "[PKCS11_MAC_EXECUTOR] "; + +// --------------------------------------------------------------------------- +// Construction +// --------------------------------------------------------------------------- + +Pkcs11MacExecutor::Pkcs11MacExecutor(const Pkcs11Module& module) noexcept + : m_module{module}, m_functionList{module.GetFunctionList()} +{ +} + +// --------------------------------------------------------------------------- +// Public entry point +// --------------------------------------------------------------------------- + +Expected Pkcs11MacExecutor::Execute( + Pkcs11MacExecutionContext& ctx, + common::OperationAction operationAction, + RequestParameters& request, + StreamOperationState currentState, + StreamOperationState& nextState) noexcept +{ + namespace ops = handler::mac_handler_operations; + const bool use_verify = (ctx.operation_mode == score::mw::crypto::OperationMode::kVerify); + + if (operationAction == ops::MAC_SS) + { + if (currentState != StreamOperationState::STREAM_INIT) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kOperationInProgress); + } + nextState = StreamOperationState::IDLE; + return use_verify ? ExecuteVerifySingleShot(ctx.session, ctx.mechanism, ctx.key_object, ctx.mac_size, request) + : ExecuteSignSingleShot(ctx.session, ctx.mechanism, ctx.key_object, ctx.mac_size, request); + } + if (operationAction == ops::MAC_INIT) + { + return HandleInit(ctx, use_verify, currentState, nextState); + } + if (operationAction == ops::MAC_UPDATE) + { + return HandleUpdate(ctx, use_verify, currentState, nextState, request); + } + if (operationAction == ops::MAC_FINAL) + { + return HandleFinal(ctx, currentState, nextState, request); + } + if (operationAction == ops::MAC_VERIFY) + { + return HandleVerify(ctx, use_verify, currentState, nextState, request); + } + + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidOperation); +} + +// --------------------------------------------------------------------------- +// Per-action helpers +// --------------------------------------------------------------------------- + +Expected Pkcs11MacExecutor::EnsureInitialized( + CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism, + CK_OBJECT_HANDLE key_object, + bool use_verify, + StreamOperationState currentState) noexcept +{ + if (currentState != StreamOperationState::STREAM_INIT) + { + return std::monostate{}; // already initialised via MAC_INIT + } + return use_verify ? ExecuteVerifyInit(session, mechanism, key_object) + : ExecuteSignInit(session, mechanism, key_object); +} + +Expected Pkcs11MacExecutor::HandleInit( + Pkcs11MacExecutionContext& ctx, + bool use_verify, + StreamOperationState currentState, + StreamOperationState& nextState) noexcept +{ + if (currentState == StreamOperationState::STREAM_ACTIVE) + { + Abort(ctx.session, ctx.operation_mode); + } + auto init_res = use_verify ? ExecuteVerifyInit(ctx.session, ctx.mechanism, ctx.key_object) + : ExecuteSignInit(ctx.session, ctx.mechanism, ctx.key_object); + if (!init_res.has_value()) + { + nextState = currentState; + return make_unexpected(init_res.error()); + } + nextState = StreamOperationState::STREAM_ACTIVE; + return ResponseParameters{}; +} + +Expected Pkcs11MacExecutor::HandleUpdate( + Pkcs11MacExecutionContext& ctx, + bool use_verify, + StreamOperationState currentState, + StreamOperationState& nextState, + RequestParameters& request) noexcept +{ + const auto validation = + ValidateStreamTransition(handler::mac_handler_operations::MAC_UPDATE, currentState, nextState); + if (!validation.has_value()) + { + return make_unexpected(validation.error()); + } + auto init_res = EnsureInitialized(ctx.session, ctx.mechanism, ctx.key_object, use_verify, currentState); + if (!init_res.has_value()) + { + nextState = currentState; + return make_unexpected(init_res.error()); + } + auto result = use_verify ? ExecuteVerifyUpdate(ctx.session, request) : ExecuteSignUpdate(ctx.session, request); + if (!result.has_value()) + { + nextState = currentState; + return make_unexpected(result.error()); + } + return ResponseParameters{}; +} + +Expected Pkcs11MacExecutor::HandleFinal( + Pkcs11MacExecutionContext& ctx, + StreamOperationState currentState, + StreamOperationState& nextState, + RequestParameters& request) noexcept +{ + // MAC_FINAL produces tag bytes — only valid on the sign (kGenerate) path. + // The verify path (kVerify) has no equivalent; use MAC_VERIFY instead. + if (ctx.operation_mode == score::mw::crypto::OperationMode::kVerify) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidOperation); + } + const auto validation = + ValidateStreamTransition(handler::mac_handler_operations::MAC_FINAL, currentState, nextState); + if (!validation.has_value()) + { + return make_unexpected(validation.error()); + } + auto result = ExecuteSignFinal(ctx.session, ctx.mac_size, request); + if (!result.has_value()) + { + nextState = currentState; + } + return result; +} + +Expected Pkcs11MacExecutor::HandleVerify( + Pkcs11MacExecutionContext& ctx, + bool use_verify, + StreamOperationState currentState, + StreamOperationState& nextState, + RequestParameters& request) noexcept +{ + if (currentState == StreamOperationState::IDLE) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kStreamNotInitialized); + } + nextState = StreamOperationState::IDLE; + + if (use_verify) + { + // C_Verify* path: perform deferred init if MAC_INIT was skipped, then finalise. + if (request.empty()) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientParameters); + } + const uint8_t* expected_tag{nullptr}; + std::size_t tag_len{0U}; + const auto e = handler::handler_utils::ExtractBufferData(request[0], expected_tag, tag_len); + if (!e.has_value()) + { + return make_unexpected(e.error()); + } + auto init_res = EnsureInitialized(ctx.session, ctx.mechanism, ctx.key_object, true, currentState); + if (!init_res.has_value()) + { + return make_unexpected(init_res.error()); + } + return ExecuteVerifyFinal(ctx.session, expected_tag, tag_len); + } + + // C_Sign* path: compute tag via C_SignFinal then constant-time compare. + const bool need_init = (currentState == StreamOperationState::STREAM_INIT); + return ExecuteSignVerify(ctx.session, ctx.mechanism, ctx.key_object, ctx.mac_size, need_init, request); +} + +// --------------------------------------------------------------------------- +// Abort +// --------------------------------------------------------------------------- + +void Pkcs11MacExecutor::Abort(CK_SESSION_HANDLE session, score::mw::crypto::OperationMode operation_mode) noexcept +{ + if (m_functionList == nullptr) + { + return; + } + + if (operation_mode == score::mw::crypto::OperationMode::kVerify) + { + // Abort an active C_Verify* operation by calling C_VerifyFinal with a dummy tag. + // C_VerifyFinal terminates the active operation regardless of whether the tag + // matches; CKR_SIGNATURE_INVALID or CKR_OK are both acceptable — the goal is + // to return the session to IDLE state. + constexpr CK_ULONG kDummyTagLen{64U}; + CK_BYTE dummy_tag[kDummyTagLen]{}; + (void)m_functionList->C_VerifyFinal(session, dummy_tag, kDummyTagLen); + return; + } + + // Abort an active C_Sign* operation by calling C_SignFinal with a real output buffer + // and discarding the result. See original Abort() comment for rationale. + constexpr CK_ULONG kMaxHmacOutputBytes{64U}; + CK_BYTE output[kMaxHmacOutputBytes]{}; + CK_ULONG sig_len{kMaxHmacOutputBytes}; + (void)m_functionList->C_SignFinal(session, output, &sig_len); +} + +// --------------------------------------------------------------------------- +// State-machine helper +// --------------------------------------------------------------------------- + +// static +Expected Pkcs11MacExecutor::ValidateStreamTransition( + common::OperationAction action, + StreamOperationState currentState, + StreamOperationState& nextState) noexcept +{ + namespace ops = handler::mac_handler_operations; + std::string_view opId; + if (action == ops::MAC_UPDATE) + { + opId = "UPDATE"; + } + else if (action == ops::MAC_FINAL) + { + opId = "FINISH"; + } + else + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidOperation); + } + return handler::handler_utils::ValidateStreamOperationSequence(currentState, opId, nextState); +} + +// --------------------------------------------------------------------------- +// PKCS#11 C API wrappers +// --------------------------------------------------------------------------- + +Expected Pkcs11MacExecutor::ExecuteSignInit( + CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism, + CK_OBJECT_HANDLE key_object) noexcept +{ + const CK_RV rv = m_functionList->C_SignInit(session, &mechanism, key_object); + if (rv != CKR_OK) + { + std::cerr << LOG_PREFIX << "C_SignInit failed: rv=" << static_cast(rv) << '\n'; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAlgorithmInitializationFailed); + } + return std::monostate{}; +} + +Expected Pkcs11MacExecutor::ExecuteSignUpdate( + CK_SESSION_HANDLE session, + RequestParameters& request) noexcept +{ + if (request.empty()) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientParameters); + } + + const uint8_t* data{nullptr}; + std::size_t data_len{0U}; + const auto extract = handler::handler_utils::ExtractBufferData(request[0], data, data_len); + if (!extract.has_value()) + { + return make_unexpected(extract.error()); + } + + // MISRA C++:2023 Rule 8.2.3 deviation — C_SignUpdate requires non-const pPart; + // PKCS#11 spec guarantees it will not modify the data. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + CK_BYTE_PTR p = const_cast(static_cast(static_cast(data))); + const CK_RV rv = m_functionList->C_SignUpdate(session, p, static_cast(data_len)); + if (rv != CKR_OK) + { + std::cerr << LOG_PREFIX << "C_SignUpdate failed: rv=" << static_cast(rv) << '\n'; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + return std::monostate{}; +} + +Expected Pkcs11MacExecutor::ExecuteSignFinal( + CK_SESSION_HANDLE session, + std::size_t mac_size, + RequestParameters& request) noexcept +{ + uint8_t* out_buf{nullptr}; + std::size_t out_buf_len{0U}; + // When no output buffer is provided by the caller, allocate internally and return an + // OwnedBuffer - mirroring the OpenSSL OpenSslHmacHandler::FinalizeMac() allocation path. + common::OwnedBuffer internal_buf; + + if (request.empty()) + { + internal_buf.resize(mac_size); + out_buf = internal_buf.data(); + out_buf_len = mac_size; + } + else + { + const auto extract = handler::handler_utils::ExtractOutputBufferData(request[0], out_buf, out_buf_len); + if (!extract.has_value()) + { + return make_unexpected(extract.error()); + } + + if (out_buf_len < mac_size) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + } + + CK_ULONG sig_len = static_cast(out_buf_len); + const CK_RV rv = m_functionList->C_SignFinal(session, static_cast(out_buf), &sig_len); + if (rv != CKR_OK) + { + std::cerr << LOG_PREFIX << "C_SignFinal failed: rv=" << static_cast(rv) << '\n'; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + + ResponseParameters response; + if (!internal_buf.empty()) + { + // Trim to actual output size and return as owned heap buffer. + internal_buf.resize(static_cast(sig_len)); + response.push_back(std::move(internal_buf)); + } + else + { + response.push_back(static_cast(sig_len)); + } + return response; +} + +Expected Pkcs11MacExecutor::ExecuteSignSingleShot( + CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism, + CK_OBJECT_HANDLE key_object, + std::size_t mac_size, + RequestParameters& request) noexcept +{ + if (request.size() < 2U) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientParameters); + } + + const uint8_t* data{nullptr}; + std::size_t data_len{0U}; + auto e1 = handler::handler_utils::ExtractBufferData(request[0], data, data_len); + if (!e1.has_value()) + { + return make_unexpected(e1.error()); + } + + uint8_t* out_buf{nullptr}; + std::size_t out_buf_len{0U}; + auto e2 = handler::handler_utils::ExtractOutputBufferData(request[1], out_buf, out_buf_len); + if (!e2.has_value()) + { + return make_unexpected(e2.error()); + } + + if (out_buf_len < mac_size) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + + auto init_res = ExecuteSignInit(session, mechanism, key_object); + if (!init_res.has_value()) + { + return make_unexpected(init_res.error()); + } + + // MISRA C++:2023 Rule 8.2.3 deviation — PKCS#11 C API (C_SignUpdate) requires non-const pPart. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + CK_BYTE_PTR p = const_cast(static_cast(static_cast(data))); + CK_RV rv = m_functionList->C_SignUpdate(session, p, static_cast(data_len)); + if (rv != CKR_OK) + { + std::cerr << LOG_PREFIX << "C_SignUpdate (single-shot) failed: rv=" << static_cast(rv) << '\n'; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + + CK_ULONG sig_len = static_cast(out_buf_len); + rv = m_functionList->C_SignFinal(session, static_cast(out_buf), &sig_len); + if (rv != CKR_OK) + { + std::cerr << LOG_PREFIX << "C_SignFinal (single-shot) failed: rv=" << static_cast(rv) << '\n'; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + + ResponseParameters response; + response.push_back(static_cast(sig_len)); + return response; +} + +Expected Pkcs11MacExecutor::ExecuteSignVerify( + CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism, + CK_OBJECT_HANDLE key_object, + std::size_t mac_size, + bool need_init, + RequestParameters& request) noexcept +{ + // MAC_VERIFY always carries exactly one parameter: the expected tag. + // The data to authenticate has already been submitted via MAC_UPDATE + // (C_SignUpdate) calls when need_init is false (STREAM_ACTIVE). + // When need_init is true (STREAM_INIT, i.e. MAC_INIT was skipped), + // this executes a verify of the empty message. + if (request.empty()) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientParameters); + } + + const uint8_t* expected_tag{nullptr}; + std::size_t tag_len{0U}; + const auto e = handler::handler_utils::ExtractBufferData(request[0], expected_tag, tag_len); + if (!e.has_value()) + { + return make_unexpected(e.error()); + } + + if (tag_len != mac_size) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + + if (need_init) + { + // Direct path (STREAM_INIT): C_SignInit was never called (MAC_INIT was skipped). + // Initialise the sign context now; no data precedes this verify. + auto init_res = ExecuteSignInit(session, mechanism, key_object); + if (!init_res.has_value()) + { + return make_unexpected(init_res.error()); + } + } + + // Finalise the ongoing C_Sign* operation (all data was fed via C_SignUpdate) + // and compare the computed tag against the expected one using constant-time. + // This path is only reached when operation_mode == kGenerate (use_verify == false), + // so the key is guaranteed to have CKA_SIGN=true; C_SignFinal will not fail on that basis. + std::vector computed(mac_size, 0U); + CK_ULONG sig_len = static_cast(mac_size); + const CK_RV rv = m_functionList->C_SignFinal(session, computed.data(), &sig_len); + if (rv != CKR_OK) + { + std::cerr << LOG_PREFIX << "C_SignFinal (verify) failed: rv=" << static_cast(rv) << '\n'; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + + // Constant-time comparison. + uint8_t diff{0U}; + for (std::size_t i = 0U; i < static_cast(sig_len); ++i) + { + diff |= computed[i] ^ expected_tag[i]; + } + ResponseParameters response; + response.push_back(diff == 0U); + return response; +} + +Expected Pkcs11MacExecutor::ExecuteVerifyInit( + CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism, + CK_OBJECT_HANDLE key_object) noexcept +{ + const CK_RV rv = m_functionList->C_VerifyInit(session, &mechanism, key_object); + if (rv != CKR_OK) + { + std::cerr << LOG_PREFIX << "C_VerifyInit failed: rv=" << static_cast(rv) << '\n'; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAlgorithmInitializationFailed); + } + return std::monostate{}; +} + +Expected Pkcs11MacExecutor::ExecuteVerifyUpdate( + CK_SESSION_HANDLE session, + RequestParameters& request) noexcept +{ + if (request.empty()) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientParameters); + } + + const uint8_t* data{nullptr}; + std::size_t data_len{0U}; + const auto extract = handler::handler_utils::ExtractBufferData(request[0], data, data_len); + if (!extract.has_value()) + { + return make_unexpected(extract.error()); + } + + // MISRA C++:2023 Rule 8.2.3 deviation — C_VerifyUpdate requires non-const pPart; + // PKCS#11 spec guarantees it will not modify the data. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + CK_BYTE_PTR p = const_cast(static_cast(static_cast(data))); + const CK_RV rv = m_functionList->C_VerifyUpdate(session, p, static_cast(data_len)); + if (rv != CKR_OK) + { + std::cerr << LOG_PREFIX << "C_VerifyUpdate failed: rv=" << static_cast(rv) << '\n'; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + return std::monostate{}; +} + +Expected Pkcs11MacExecutor::ExecuteVerifyFinal( + CK_SESSION_HANDLE session, + const uint8_t* expected_tag, + std::size_t tag_len) noexcept +{ + // C_VerifyFinal takes the expected signature directly and returns CKR_OK + // if it matches or CKR_SIGNATURE_INVALID otherwise — no tag materialisation + // on the daemon side and no timing side-channel via heap comparison. + // MISRA C++:2023 Rule 8.2.3 deviation — PKCS#11 C_VerifyFinal pSignature is non-const by spec. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + CK_BYTE_PTR tag_ptr = const_cast(static_cast(static_cast(expected_tag))); + const CK_RV rv = m_functionList->C_VerifyFinal(session, tag_ptr, static_cast(tag_len)); + if (rv == CKR_SIGNATURE_INVALID) + { + ResponseParameters response; + response.push_back(static_cast(0U)); // mismatch + return response; + } + if (rv != CKR_OK) + { + std::cerr << LOG_PREFIX << "C_VerifyFinal failed: rv=" << static_cast(rv) << '\n'; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + ResponseParameters response; + response.push_back(static_cast(1U)); // match + return response; +} + +Expected Pkcs11MacExecutor::ExecuteVerifySingleShot( + CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism, + CK_OBJECT_HANDLE key_object, + std::size_t mac_size, + RequestParameters& request) noexcept +{ + // Symmetric counterpart of ExecuteSignSingleShot for keys with CKA_VERIFY=true. + // Uses C_VerifyInit + C_VerifyUpdate + C_VerifyFinal so the comparison is performed + // inside the HSM — no tag materialisation on the daemon side. + if (request.size() < 2U) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientParameters); + } + + const uint8_t* data{nullptr}; + std::size_t data_len{0U}; + const auto e1 = handler::handler_utils::ExtractBufferData(request[0], data, data_len); + if (!e1.has_value()) + { + return make_unexpected(e1.error()); + } + + const uint8_t* expected_tag{nullptr}; + std::size_t tag_len{0U}; + const auto e2 = handler::handler_utils::ExtractBufferData(request[1], expected_tag, tag_len); + if (!e2.has_value()) + { + return make_unexpected(e2.error()); + } + + if (tag_len != mac_size) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + + auto init_res = ExecuteVerifyInit(session, mechanism, key_object); + if (!init_res.has_value()) + { + return make_unexpected(init_res.error()); + } + + // MISRA C++:2023 Rule 8.2.3 deviation — PKCS#11 C API (C_VerifyUpdate) requires non-const pPart. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + CK_BYTE_PTR p = const_cast(static_cast(static_cast(data))); + const CK_RV rv_upd = m_functionList->C_VerifyUpdate(session, p, static_cast(data_len)); + if (rv_upd != CKR_OK) + { + std::cerr << LOG_PREFIX + << "C_VerifyUpdate (single-shot verify) failed: rv=" << static_cast(rv_upd) << '\n'; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + + return ExecuteVerifyFinal(session, expected_tag, tag_len); +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.hpp b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.hpp new file mode 100644 index 0000000..57288c2 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.hpp @@ -0,0 +1,213 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_MAC_EXECUTOR_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_MAC_EXECUTOR_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_context.hpp" +#include "score/mw/crypto/api/common/types.hpp" // OperationMode + +#include +#include + +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +class Pkcs11Module; + +/// @brief Executor (visitor) that translates generic MAC operation IDs to +/// PKCS#11 C_Sign* / C_Verify* calls. +/// +/// ### Operation mapping +/// MAC_INIT → C_SignInit (eagerly, moves to STREAM_ACTIVE) +/// MAC_UPDATE → C_SignUpdate +/// MAC_FINAL → C_SignFinal (returns MAC bytes) +/// MAC_VERIFY → C_SignFinal + constant-time compare with expected tag +/// MAC_SS → C_SignInit + C_SignUpdate + C_SignFinal (single-shot) +/// +/// ### Key usage and verify path +/// The current streaming verify path (Init → Update → Verify) feeds data via +/// C_Sign* and finalises with C_SignFinal, then compares. This requires +/// CKA_SIGN=true on the key. +/// +/// PKCS#11 also provides symmetric C_Verify* APIs (C_VerifyInit / +/// C_VerifyUpdate / C_VerifyFinal) that require only CKA_VERIFY=true and +/// avoid materialising the computed tag. Full use of C_Verify* for the +/// streaming path would require knowing at MAC_INIT time whether the +/// operation will end with MAC_FINAL or MAC_VERIFY — which in turn requires +/// either a dual-context approach or deferred init. +/// +/// ExecuteVerifyInit / ExecuteVerifyFinal are provided as first-class +/// wrappers for the direct-verify (STREAM_INIT → MAC_VERIFY) path and +/// future dual-context support. +/// +/// ### C_SignInit placement +/// C_SignInit is NOT called in InitializeContext(); it is deferred to the +/// first MAC_UPDATE (or MAC_INIT if called explicitly). All PKCS#11 +/// dispatch lives inside this executor. +class Pkcs11MacExecutor final +{ + public: + /// @brief Construct executor with a reference to the initialised PKCS#11 module. + explicit Pkcs11MacExecutor(const Pkcs11Module& module) noexcept; + + ~Pkcs11MacExecutor() = default; + + Pkcs11MacExecutor(const Pkcs11MacExecutor&) = delete; + Pkcs11MacExecutor& operator=(const Pkcs11MacExecutor&) = delete; + Pkcs11MacExecutor(Pkcs11MacExecutor&&) noexcept = default; + Pkcs11MacExecutor& operator=(Pkcs11MacExecutor&&) noexcept = default; + + /// @brief Dispatch a MAC operation to the corresponding C_Sign* or C_Verify* call(s). + /// + /// @param ctx Stable per-context parameters (session, mechanism, key, mac_size, mode). + /// @param operationAction MAC operation action (MAC_UPDATE, MAC_FINAL, etc.). + /// @param request Operation config with input/output parameters. + /// @param currentState Current streaming state of the handler. + /// @param nextState Output: the state the handler should transition to on success. + [[nodiscard]] Expected Execute( + Pkcs11MacExecutionContext& ctx, + common::OperationAction operationAction, + common::RequestParameters& request, + common::StreamOperationState currentState, + common::StreamOperationState& nextState) noexcept; + + /// @brief Abort any active sign or verify operation. + /// + /// For the sign path (kGenerate), calls C_SignFinal with a dummy buffer. + /// For the verify path (kVerify), calls C_VerifyFinal with a dummy tag. + /// Safe to call when no operation is active (error ignored). + void Abort(CK_SESSION_HANDLE session, score::mw::crypto::OperationMode operation_mode) noexcept; + + private: + /// @brief Validate a streaming action against the current state. + [[nodiscard]] static Expected + ValidateStreamTransition(common::OperationAction action, + common::StreamOperationState currentState, + common::StreamOperationState& nextState) noexcept; + + /// @brief Call C_SignInit or C_VerifyInit depending on use_verify. + /// Calls C_SignInit when STREAM_INIT (deferred init from InitializeContext); + /// no-op when STREAM_ACTIVE (already initialised via MAC_INIT). + [[nodiscard]] Expected EnsureInitialized( + CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism, + CK_OBJECT_HANDLE key_object, + bool use_verify, + common::StreamOperationState currentState) noexcept; + + // --- Per-action dispatch helpers (called from Execute) --------------------- + + [[nodiscard]] Expected HandleInit( + Pkcs11MacExecutionContext& ctx, + bool use_verify, + common::StreamOperationState currentState, + common::StreamOperationState& nextState) noexcept; + + [[nodiscard]] Expected HandleUpdate( + Pkcs11MacExecutionContext& ctx, + bool use_verify, + common::StreamOperationState currentState, + common::StreamOperationState& nextState, + common::RequestParameters& request) noexcept; + + [[nodiscard]] Expected HandleFinal( + Pkcs11MacExecutionContext& ctx, + common::StreamOperationState currentState, + common::StreamOperationState& nextState, + common::RequestParameters& request) noexcept; + + [[nodiscard]] Expected HandleVerify( + Pkcs11MacExecutionContext& ctx, + bool use_verify, + common::StreamOperationState currentState, + common::StreamOperationState& nextState, + common::RequestParameters& request) noexcept; + + /// @brief Call C_SignInit(session, mechanism, key_object). + [[nodiscard]] Expected + ExecuteSignInit(CK_SESSION_HANDLE session, CK_MECHANISM& mechanism, CK_OBJECT_HANDLE key_object) noexcept; + + /// @brief Call C_SignUpdate with data from request[0]. + [[nodiscard]] Expected ExecuteSignUpdate( + CK_SESSION_HANDLE session, + common::RequestParameters& request) noexcept; + + /// @brief Call C_SignFinal, writing output to request[0]. + [[nodiscard]] Expected + ExecuteSignFinal(CK_SESSION_HANDLE session, std::size_t mac_size, common::RequestParameters& request) noexcept; + + /// @brief Perform a complete single-shot sign: C_SignInit + C_SignUpdate + C_SignFinal. + /// Data buffer in request[0], output in request[1]. + [[nodiscard]] Expected + ExecuteSignSingleShot(CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism, + CK_OBJECT_HANDLE key_object, + std::size_t mac_size, + common::RequestParameters& request) noexcept; + + /// @brief Compute MAC over streamed data and compare with expected tag (constant-time). + /// MAC_VERIFY always receives exactly one parameter: the expected tag. + /// When need_init is true (STREAM_INIT), C_SignInit is called first + /// (HMAC of empty data); when false (STREAM_ACTIVE), data was already + /// fed via C_SignUpdate — this call finalises and compares. + /// + /// NOTE: requires CKA_SIGN=true. For keys with CKA_SIGN=false / + /// CKA_VERIFY=true use ExecuteVerifyFinal() after a C_VerifyInit + + /// C_VerifyUpdate chain. See TODO in pkcs11_mac_executor.cpp. + [[nodiscard]] Expected + ExecuteSignVerify(CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism, + CK_OBJECT_HANDLE key_object, + std::size_t mac_size, + bool need_init, + common::RequestParameters& request) noexcept; + + /// @brief Call C_VerifyInit(session, mechanism, key_object). + /// Use when the key has CKA_VERIFY=true. + [[nodiscard]] Expected + ExecuteVerifyInit(CK_SESSION_HANDLE session, CK_MECHANISM& mechanism, CK_OBJECT_HANDLE key_object) noexcept; + + /// @brief Call C_VerifyUpdate with data from request[0]. + /// Symmetric counterpart of ExecuteSignUpdate for the verify path. + [[nodiscard]] Expected ExecuteVerifyUpdate( + CK_SESSION_HANDLE session, + common::RequestParameters& request) noexcept; + + /// @brief Call C_VerifyFinal with the expected tag. + /// Returns ResponseParameters{uint64: 1=match, 0=mismatch}. + /// Must be preceded by ExecuteVerifyInit + zero or more ExecuteVerifyUpdate calls. + [[nodiscard]] Expected + ExecuteVerifyFinal(CK_SESSION_HANDLE session, const uint8_t* expected_tag, std::size_t tag_len) noexcept; + + /// @brief Single-shot verify: C_VerifyInit + C_VerifyUpdate + C_VerifyFinal. + /// Symmetric counterpart of ExecuteSignSingleShot for keys with CKA_VERIFY=true. + /// Data in request[0], expected tag in request[1]. + [[nodiscard]] Expected + ExecuteVerifySingleShot(CK_SESSION_HANDLE session, + CK_MECHANISM& mechanism, + CK_OBJECT_HANDLE key_object, + std::size_t mac_size, + common::RequestParameters& request) noexcept; + + const Pkcs11Module& m_module; + CK_FUNCTION_LIST* m_functionList; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_MAC_EXECUTOR_HPP diff --git a/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_handler.cpp b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_handler.cpp new file mode 100644 index 0000000..a54f5a0 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_handler.cpp @@ -0,0 +1,256 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= +#include "score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_handler.hpp" + +#include "score/crypto/daemon/common/algorithm_info.hpp" +#include "score/crypto/daemon/provider/pkcs11/detail/pkcs11_algorithm_info.hpp" +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp" + +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +using namespace score::crypto::daemon::provider::handler::mac_handler_operations; // NOLINT +using common::OperationIdentifier; +using common::RequestParameters; +using common::ResponseParameters; +using common::StreamOperationState; +using score::crypto::daemon::common::DaemonErrorCode; + +// --------------------------------------------------------------------------- +// Supported algorithms +// --------------------------------------------------------------------------- +static constexpr const char* kSupportedAlgorithms[] = { + "HMAC-SHA256", + "HMAC-SHA384", + "HMAC-SHA512", +}; + +// --------------------------------------------------------------------------- +// Algorithm mapping +// --------------------------------------------------------------------------- +CK_MECHANISM_TYPE Pkcs11MacHandler::MapAlgorithm(const std::string_view algorithm) noexcept +{ + return detail::LookupMacMechanism(algorithm); +} + +// --------------------------------------------------------------------------- +// Construction / destruction +// --------------------------------------------------------------------------- +Pkcs11MacHandler::Pkcs11MacHandler(std::unique_ptr executor, + const Pkcs11Module& module, + CK_SESSION_HANDLE session, + const common::AlgorithmId& algorithm, + Pkcs11Provider* provider) + : Handler{}, + m_executor{std::move(executor)}, + m_module{module}, + m_session{session}, + m_provider{provider}, + m_ctx{}, + m_algorithm{algorithm} +{ + m_ctx.mechanism = {MapAlgorithm(algorithm), nullptr, 0U}; +} + +Pkcs11MacHandler::~Pkcs11MacHandler() +{ + // Abort any in-progress operation on the operational session before releasing. + if (m_state != common::StreamOperationState::IDLE && m_op_session != CK_INVALID_HANDLE) + { + m_executor->Abort(m_op_session, m_ctx.operation_mode); + } + // Release the key lock (for session objects) before returning m_session to pool. + m_resolved_key = {}; + if (m_provider != nullptr && m_session != CK_INVALID_HANDLE) + { + m_provider->ReleaseSession(m_session, kRequirements); + } +} + +// --------------------------------------------------------------------------- +// Static helpers +// --------------------------------------------------------------------------- +bool Pkcs11MacHandler::IsAlgorithmSupported(const common::AlgorithmId& algorithm) noexcept +{ + for (const char* supported : kSupportedAlgorithms) + { + if (algorithm == supported) + { + return true; + } + } + return false; +} + +std::size_t Pkcs11MacHandler::GetMacSize() const noexcept +{ + return score::crypto::daemon::common::LookupMacSize(std::string_view{m_algorithm.data(), m_algorithm.size()}) + .value_or(0U); +} + +// --------------------------------------------------------------------------- +// Handler interface +// --------------------------------------------------------------------------- +score::crypto::Expected +Pkcs11MacHandler::InitializeContext(const handler::InitializationParams& init_params) +{ + bool found{false}; + for (const char* algo : kSupportedAlgorithms) + { + if (m_algorithm == algo) + { + found = true; + break; + } + } + if (!found) + { + std::cerr << LOG_PREFIX << "Unsupported algorithm: " << m_algorithm << '\n'; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedAlgorithm); + } + + m_ctx.mechanism.mechanism = MapAlgorithm(m_algorithm); + m_ctx.mechanism.pParameter = nullptr; + m_ctx.mechanism.ulParameterLen = 0U; + m_state = StreamOperationState::IDLE; + m_ctx.key_object = CK_INVALID_HANDLE; + + // Bind key if provided via InitializationParams. + if (init_params.bound_key_handler != nullptr) + { + // Downcast to the PKCS#11-specific key handler for direct session key access. + // Provider-id check validates the key comes from the same provider (no dynamic_cast/RTTI). + if (init_params.bound_key_handler->GetProviderId() != init_params.provider_id) + { + std::cerr << LOG_PREFIX << "Bound key provider ID " << init_params.bound_key_handler->GetProviderId() + << " does not match expected provider ID " << init_params.provider_id << '\n'; + std::cerr << LOG_PREFIX << "InitializeContext: bound key is not a PKCS#11 key handler\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) - type tag verified above + const auto* pkcs11_key = static_cast(init_params.bound_key_handler); + + // Resolve the key: for session objects tries to acquire the key's mutex + // (non-blocking); returns kResourceBusy if the key is already in use by + // another handler. For token objects runs C_FindObjects on m_session. + Pkcs11KeyStore::ResolvedKey resolved = pkcs11_key->ResolveObject(m_session); + if (resolved.contended) + { + std::cerr << LOG_PREFIX << "InitializeContext: key is already in use by another handler\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kResourceBusy); + } + if (!resolved.IsValid()) + { + std::cerr << LOG_PREFIX << "InitializeContext: failed to resolve key object handle\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + // Store the resolved session and object. C_SignInit/C_VerifyInit is deferred to the + // executor and called lazily before the first sign/verify operation. + m_op_session = resolved.session; + m_ctx.session = resolved.session; + m_ctx.key_object = resolved.object; + m_ctx.mac_size = GetMacSize(); + + // operation_mode is MAC-specific: read from param[4] of the CTX_CREATE wire call. + if (init_params.context_creation_params.size() >= 5) + { + const auto* mode_val = std::get_if(&init_params.context_creation_params[4]); + if (mode_val != nullptr) + { + m_ctx.operation_mode = static_cast(*mode_val); + } + } + + m_resolved_key = std::move(resolved); // keeps the mutex locked for session objects + m_state = StreamOperationState::STREAM_INIT; + m_init_params = init_params; // Saved so Reset() can restore the key binding. + } + + return std::monostate{}; +} + +score::crypto::Expected Pkcs11MacHandler::Reset() +{ + if (m_state != StreamOperationState::IDLE) + { + m_executor->Abort(m_op_session, m_ctx.operation_mode); + } + // Release the key lock before calling InitializeContext so that it can + // re-acquire it cleanly (avoids self-deadlock on the same mutex). + m_resolved_key = {}; + m_op_session = CK_INVALID_HANDLE; + m_ctx.key_object = CK_INVALID_HANDLE; + m_ctx.session = CK_INVALID_HANDLE; + m_state = StreamOperationState::IDLE; + + // Re-run InitializeContext with the saved params to restore the key binding + // and return to STREAM_INIT, matching the OpenSSL handler Reset() semantics. + return InitializeContext(m_init_params); +} + +// --------------------------------------------------------------------------- +// Generic Execute dispatch +// --------------------------------------------------------------------------- +score::crypto::Expected Pkcs11MacHandler::Execute( + const OperationIdentifier& operationId, + RequestParameters& request) +{ + namespace ops = handler::mac_handler_operations; + + // Validate session is still open before any PKCS#11 call. + if ((m_provider != nullptr) && !m_provider->ValidateSession(m_session)) + { + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kSessionInvalid); + } + + // MAC_GET_SIZE is a stateless size query; no PKCS#11 call needed. + if (operationId.operationAction == ops::MAC_GET_SIZE) + { + ResponseParameters response; + response.push_back(static_cast(GetMacSize())); + return response; + } + + // MAC_RESET - abort any in-progress sign/verify operation and restore key binding. + if (operationId.operationAction == ops::MAC_RESET) + { + auto res = Reset(); + if (!res.has_value()) + { + return score::crypto::make_unexpected(res.error()); + } + return ResponseParameters{}; + } + + if (m_ctx.key_object == CK_INVALID_HANDLE) + { + std::cerr << LOG_PREFIX << "Execute: no key bound - call InitializeContext with keyOpaqueId first\n"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kStreamNotInitialized); + } + + StreamOperationState nextState{m_state}; + auto result = m_executor->Execute(m_ctx, operationId.operationAction, request, m_state, nextState); + if (result.has_value()) + { + m_state = nextState; + } + return result; +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_handler.hpp b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_handler.hpp new file mode 100644 index 0000000..7785ed3 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_handler.hpp @@ -0,0 +1,129 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_MAC_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_MAC_HANDLER_HPP + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/handler/i_handler.hpp" +#include "score/crypto/daemon/provider/handler/operations/mac_handler_operations.hpp" +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_context.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +// Forward declarations +class Pkcs11Provider; + +/// @brief PKCS#11 MAC handler using C_Sign* / C_Verify* operations. +/// +/// All operations are dispatched to Pkcs11MacExecutor which calls the PKCS#11 C API directly. +/// Supports HMAC-SHA256, HMAC-SHA384, HMAC-SHA512 and can be extended for other PKCS#11 +/// MAC mechanisms without changes to callers. +/// +/// Operation mode (OperationMode::kGenerate vs kVerify) is selected at InitializeContext() +/// time: kGenerate uses C_Sign*, kVerify uses C_Verify* — allowing keys with +/// CKA_SIGN=false / CKA_VERIFY=true on the verify path. +/// +/// Key binding is handled inside InitializeContext(): the daemon opaque_id is resolved +/// to CK_OBJECT_HANDLE via Pkcs11KeyStore; C_SignInit / C_VerifyInit are deferred to the +/// executor and called lazily before the first data operation. +/// +/// Each instance owns a dedicated PKCS#11 session (ReadOnly/User tier) acquired +/// at construction via the provider's session pool. The session is returned on +/// destruction, mirroring the Pkcs11HashHandler lifecycle. +class Pkcs11MacHandler final : public handler::Handler +{ + public: + /// @brief Session / auth requirements for MAC operations. + /// + /// MAC operations require a logged-in session because the key is CKA_SENSITIVE. + static constexpr Pkcs11HandlerRequirements kRequirements{Pkcs11SessionType::ReadOnly, Pkcs11TokenAuthState::User}; + + /// @brief Construct handler. + /// @param executor Unique pointer to MAC executor for PKCS#11 operations. + /// @param module Reference to the initialised PKCS#11 module (non-owning). + /// @param session Session acquired from the provider pool. + /// @param algorithm Algorithm ID (e.g. "HMAC-SHA256"). + /// @param provider Non-owning pointer to parent provider (for session release on destruction). + explicit Pkcs11MacHandler(std::unique_ptr executor, + const Pkcs11Module& module, + CK_SESSION_HANDLE session, + const common::AlgorithmId& algorithm, + Pkcs11Provider* provider); + + ~Pkcs11MacHandler() override; + + Pkcs11MacHandler(const Pkcs11MacHandler&) = delete; + Pkcs11MacHandler& operator=(const Pkcs11MacHandler&) = delete; + Pkcs11MacHandler(Pkcs11MacHandler&&) = delete; + Pkcs11MacHandler& operator=(Pkcs11MacHandler&&) = delete; + + // ----------------------------------------------------------------------- + // Handler interface + // ----------------------------------------------------------------------- + + [[nodiscard]] score::crypto::Expected + InitializeContext(const handler::InitializationParams& init_params) override; + + [[nodiscard]] score::crypto::Expected + Execute(const common::OperationIdentifier& operationId, common::RequestParameters& request) override; + + [[nodiscard]] score::crypto::Expected Reset() + override; + + /// @brief Returns static handler configuration for registration in the factory. + /// @brief Check if the given algorithm is supported by this handler. + [[nodiscard]] static bool IsAlgorithmSupported(const common::AlgorithmId& algorithm) noexcept; + + private: + /// @brief Map algorithm ID → CK_MECHANISM_TYPE for HMAC. + [[nodiscard]] static CK_MECHANISM_TYPE MapAlgorithm(std::string_view algorithm) noexcept; + + /// @brief MAC output size for the active algorithm (used internally). + [[nodiscard]] std::size_t GetMacSize() const noexcept; + + std::unique_ptr m_executor; + const Pkcs11Module& m_module; + CK_SESSION_HANDLE m_session; ///< handler's own session (used for token keys) + Pkcs11Provider* m_provider; ///< non-owning; for ReleaseSession + Pkcs11MacExecutionContext m_ctx; ///< stable per-context parameters for executor + /// Session actually used for C_Sign* calls. For session-object keys this is + /// the creating session (from ResolvedKey); for token keys it is m_session. + CK_SESSION_HANDLE m_op_session{CK_INVALID_HANDLE}; + /// Holds the per-key mutex lock while a session-object key is bound. Empty + /// for token keys. Released (and re-acquired) on Reset(). + Pkcs11KeyStore::ResolvedKey m_resolved_key; + common::AlgorithmId m_algorithm; + common::StreamOperationState m_state{common::StreamOperationState::IDLE}; + handler::InitializationParams m_init_params; ///< saved for Reset() + + static constexpr std::string_view LOG_PREFIX = "[PKCS11_MAC_HANDLER] "; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_MAC_HANDLER_HPP diff --git a/score/crypto/daemon/provider/pkcs11/pkcs11_module.cpp b/score/crypto/daemon/provider/pkcs11/pkcs11_module.cpp new file mode 100644 index 0000000..bd4b9b1 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_module.cpp @@ -0,0 +1,465 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" + +#include "score/crypto/common/types.hpp" + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +// ============================================================================ +// TokenAuthGuard +// ============================================================================ + +TokenAuthGuard::TokenAuthGuard(std::string_view pin) noexcept : m_pin{pin} {} + +Expected TokenAuthGuard::EnsureUserState( + const CK_SESSION_HANDLE anySession, + const Pkcs11Module& module) noexcept +{ + std::lock_guard lock(m_mutex); + CK_FUNCTION_LIST* const functionList = module.GetFunctionList(); + if (m_state == Pkcs11TokenAuthState::User) + { + // Already logged in — token is in User state for all sessions. + m_activeUserCount++; + return std::monostate{}; + } + + // Attempt C_Login via the stored function list — never through direct C-linkage — + // so that the call correctly targets the library that owns this session. + // + // An empty PIN is valid for some PKCS#11 implementations (e.g. certain + // smart-card middleware). Per PKCS#11 spec, pPin may be NULL_PTR when + // ulPinLen is 0. We pass nullptr explicitly for the empty case so that + // implementations that dereference pPin unconditionally do not fault. + // MISRA C++:2023 Rule 8.2.3 + Rule 8.2.4 deviation — PKCS#11 C_Login requires CK_UTF8CHAR_PTR + // (non-const). reinterpret_cast is needed because CK_UTF8CHAR is unsigned char while + // std::string stores char. The token will not modify the PIN data. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast) + auto* pinData = m_pin.empty() ? static_cast(nullptr) + : const_cast(reinterpret_cast(m_pin.data())); + const auto pinLen = static_cast(m_pin.size()); + + const CK_RV rv = functionList->C_Login(anySession, CKU_USER, pinData, pinLen); + if ((rv != CKR_OK) && (rv != CKR_USER_ALREADY_LOGGED_IN)) + { + return make_unexpected(Pkcs11Module::MapErrorReturn(rv)); + } + + m_state = Pkcs11TokenAuthState::User; + m_activeUserCount++; + return std::monostate{}; +} + +void TokenAuthGuard::OnUserHandlerReleased(const CK_SESSION_HANDLE anySession, const Pkcs11Module& module) noexcept +{ + std::lock_guard lock(m_mutex); + CK_FUNCTION_LIST* const functionList = module.GetFunctionList(); + if (m_activeUserCount == 0U) + { + return; // should not happen; guard against underflow + } + + m_activeUserCount--; + + if (m_activeUserCount == 0U) + { + m_state = Pkcs11TokenAuthState::Public; + if (anySession != CK_INVALID_HANDLE) + { + static_cast(functionList->C_Logout(anySession)); + } + } +} + +Pkcs11TokenAuthState TokenAuthGuard::GetCurrentState() const noexcept +{ + std::lock_guard lock(m_mutex); + return m_state; +} + +std::uint32_t TokenAuthGuard::GetActiveUserHandlerCount() const noexcept +{ + std::lock_guard lock(m_mutex); + return m_activeUserCount; +} + +// ============================================================================ +// ModuleGuard +// ============================================================================ + +ModuleGuard::ModuleGuard() noexcept : m_initialized{false} {} + +ModuleGuard::~ModuleGuard() +{ + if (m_initialized) + { + static_cast(C_Finalize(nullptr)); + m_initialized = false; + } +} + +ModuleGuard::ModuleGuard(ModuleGuard&& other) noexcept : m_initialized{other.m_initialized} +{ + other.m_initialized = false; +} + +ModuleGuard& ModuleGuard::operator=(ModuleGuard&& other) noexcept +{ + if (this != &other) + { + if (m_initialized) + { + static_cast(C_Finalize(nullptr)); + } + m_initialized = other.m_initialized; + other.m_initialized = false; + } + return *this; +} + +Expected ModuleGuard::Initialize( + CK_C_INITIALIZE_ARGS* initArgs) noexcept +{ + if (m_initialized) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAlreadyInitialized); + } + + const CK_RV rv = C_Initialize(static_cast(initArgs)); + if (rv == CKR_CRYPTOKI_ALREADY_INITIALIZED) + { + // Library was already initialised by another Pkcs11Module instance in this process. + // This guard must NOT claim ownership of C_Finalize — it would prematurely + // tear down the library while other sessions are still open. + // Solution: share a single Pkcs11Module (via shared_ptr) across all providers + // that use the same linked PKCS#11 library. + return std::monostate{}; + } + if (rv != CKR_OK) + { + return make_unexpected(Pkcs11Module::MapErrorReturn(rv)); + } + + m_initialized = true; // This guard exclusively owns C_Finalize. + return std::monostate{}; +} + +bool ModuleGuard::IsInitialized() const noexcept +{ + return m_initialized; +} + +// ============================================================================ +// SessionGuard +// ============================================================================ + +SessionGuard::SessionGuard() noexcept : m_functionList{nullptr}, m_session{CK_INVALID_HANDLE}, m_open{false} {} + +SessionGuard::~SessionGuard() +{ + Close(); +} + +SessionGuard::SessionGuard(SessionGuard&& other) noexcept + : m_functionList{other.m_functionList}, m_session{other.m_session}, m_open{other.m_open} +{ + other.m_functionList = nullptr; + other.m_session = CK_INVALID_HANDLE; + other.m_open = false; +} + +SessionGuard& SessionGuard::operator=(SessionGuard&& other) noexcept +{ + if (this != &other) + { + Close(); + m_functionList = other.m_functionList; + m_session = other.m_session; + m_open = other.m_open; + other.m_functionList = nullptr; + other.m_session = CK_INVALID_HANDLE; + other.m_open = false; + } + return *this; +} + +Expected +SessionGuard::Open(const Pkcs11Module& module, const CK_SLOT_ID slotId, const Pkcs11SessionType sessionType) noexcept +{ + if (m_open) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kAlreadyInitialized); + } + + CK_FLAGS flags = CKF_SERIAL_SESSION; + if (sessionType == Pkcs11SessionType::ReadWrite) + { + flags |= CKF_RW_SESSION; + } + + // Cache the raw pointer now — Close() may be called from the destructor after + // the Pkcs11Module has been destroyed during shutdown, so we must not retain + // a reference to the module itself. + CK_FUNCTION_LIST* const fl = module.GetFunctionList(); + const CK_RV rv = fl->C_OpenSession(slotId, flags, nullptr, nullptr, &m_session); + if (rv != CKR_OK) + { + return make_unexpected(Pkcs11Module::MapErrorReturn(rv)); + } + + m_functionList = fl; // cached for Close() + m_open = true; + return std::monostate{}; +} + +CK_SESSION_HANDLE SessionGuard::Get() const noexcept +{ + return m_session; +} + +bool SessionGuard::IsOpen() const noexcept +{ + return m_open; +} + +void SessionGuard::Close() noexcept +{ + if (m_open) + { + static_cast(m_functionList->C_CloseSession(m_session)); + m_functionList = nullptr; + m_session = CK_INVALID_HANDLE; + m_open = false; + } +} + +// ============================================================================ +// Pkcs11Module +// ============================================================================ + +Pkcs11Module::Pkcs11Module() noexcept : m_functionList{nullptr}, m_moduleGuard{}, m_capabilities{} {} + +Expected Pkcs11Module::Init( + CK_C_INITIALIZE_ARGS* initArgs) noexcept +{ + // Obtain the function list from the linked library + const CK_RV rv = C_GetFunctionList(&m_functionList); + if (rv != CKR_OK) + { + return make_unexpected(MapErrorReturn(rv)); + } + + if (m_functionList == nullptr) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUninitializedStack); + } + + // Initialize the PKCS#11 module with optional init args + const auto initResult = m_moduleGuard.Initialize(initArgs); + if (!initResult.has_value()) + { + return initResult; + } + + // Query library info for version and capabilities + CK_INFO info{}; + const CK_RV infoRv = m_functionList->C_GetInfo(&info); + if (infoRv == CKR_OK) + { + m_capabilities.versionMajor = info.cryptokiVersion.major; + m_capabilities.versionMinor = info.cryptokiVersion.minor; + + // PKCS#11 v3.0+ supports C_MessageDigest* APIs + constexpr std::uint8_t kPkcs11V3Major{3U}; + m_capabilities.supportsMessageDigest = (m_capabilities.versionMajor >= kPkcs11V3Major); + } + + return std::monostate{}; +} + +CK_FUNCTION_LIST* Pkcs11Module::GetFunctionList() const noexcept +{ + return m_functionList; +} + +bool Pkcs11Module::IsInitialized() const noexcept +{ + return m_functionList != nullptr; +} + +const Pkcs11Capabilities& Pkcs11Module::GetCapabilities() const noexcept +{ + return m_capabilities; +} + +score::crypto::daemon::common::DaemonErrorCode Pkcs11Module::MapErrorReturn(const CK_RV rv) noexcept +{ + switch (rv) + { + case CKR_OK: + std::cerr << "[PKCS11_MODULE] ERROR: Trying to map a success code to an error. This should not happen\n"; + return score::crypto::daemon::common::DaemonErrorCode::kOperationFailed; + + // Session / state errors + case CKR_SESSION_HANDLE_INVALID: // fallthrough + case CKR_SESSION_CLOSED: // fallthrough + case CKR_SESSION_READ_ONLY: + return score::crypto::daemon::common::DaemonErrorCode::kInvalidState; + + // Initialisation errors + case CKR_CRYPTOKI_NOT_INITIALIZED: + return score::crypto::daemon::common::DaemonErrorCode::kUninitializedStack; + case CKR_CRYPTOKI_ALREADY_INITIALIZED: + return score::crypto::daemon::common::DaemonErrorCode::kAlreadyInitialized; + + // Operation errors + case CKR_OPERATION_NOT_INITIALIZED: + return score::crypto::daemon::common::DaemonErrorCode::kStreamNotInitialized; + case CKR_OPERATION_ACTIVE: + return score::crypto::daemon::common::DaemonErrorCode::kOperationInProgress; + case CKR_FUNCTION_NOT_SUPPORTED: + return score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation; + + // Parameter / data errors + case CKR_ARGUMENTS_BAD: // fallthrough + case CKR_DATA_INVALID: // fallthrough + case CKR_DATA_LEN_RANGE: + return score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument; + + // Mechanism / algorithm errors + case CKR_MECHANISM_INVALID: // fallthrough + case CKR_MECHANISM_PARAM_INVALID: + return score::crypto::daemon::common::DaemonErrorCode::kUnsupportedAlgorithm; + + // Buffer errors + case CKR_BUFFER_TOO_SMALL: + return score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize; + + // Resource errors + case CKR_HOST_MEMORY: // fallthrough + case CKR_DEVICE_MEMORY: + return score::crypto::daemon::common::DaemonErrorCode::kAllocationFailed; + case CKR_SESSION_COUNT: // fallthrough + case CKR_TOKEN_NOT_PRESENT: + return score::crypto::daemon::common::DaemonErrorCode::kQuotaExceeded; + + // Authentication + case CKR_PIN_INCORRECT: // fallthrough + case CKR_PIN_LOCKED: // fallthrough + case CKR_USER_NOT_LOGGED_IN: + return score::crypto::daemon::common::DaemonErrorCode::kInvalidContext; + + // General / device failures + case CKR_DEVICE_ERROR: // fallthrough + case CKR_DEVICE_REMOVED: // fallthrough + case CKR_TOKEN_NOT_RECOGNIZED: // fallthrough + case CKR_GENERAL_ERROR: // fallthrough + case CKR_FUNCTION_FAILED: // fallthrough + default: + return score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed; + } +} + +// ============================================================================ +// Pkcs11Module::FindSlotByToken +// ============================================================================ + +/// @brief Trim trailing spaces from a PKCS#11 padded label/model field. +static std::string TrimTrailingSpaces(const unsigned char* field, std::size_t len) noexcept +{ + // PKCS#11 pads label/model to fixed width with trailing spaces. + while ((len > 0U) && (field[len - 1U] == ' ')) + { + --len; + } + // MISRA C++:2023 Rule 8.2.4 deviation — PKCS#11 CK_UTF8CHAR (unsigned char) → char for std::string. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return std::string(reinterpret_cast(field), len); +} + +Expected Pkcs11Module::FindSlotByToken( + const Pkcs11Module& module, + const std::string_view tokenLabel, + const std::string_view tokenModel) noexcept +{ + CK_FUNCTION_LIST* const functionList = module.GetFunctionList(); + + // 1. Enumerate slots that have a token present. + CK_ULONG slotCount{0U}; + CK_RV rv = functionList->C_GetSlotList(CK_TRUE, nullptr, &slotCount); + if (rv != CKR_OK) + { + return make_unexpected(MapErrorReturn(rv)); + } + + if (slotCount == 0U) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kQuotaExceeded); + } + + std::vector slots(slotCount); + rv = functionList->C_GetSlotList(CK_TRUE, slots.data(), &slotCount); + if (rv != CKR_OK) + { + return make_unexpected(MapErrorReturn(rv)); + } + + // 2. Iterate slots, match label (+ optional model), skip uninitialised tokens. + for (CK_ULONG i = 0U; i < slotCount; ++i) + { + CK_TOKEN_INFO tokenInfo{}; + rv = functionList->C_GetTokenInfo(slots[i], &tokenInfo); + if (rv != CKR_OK) + { + continue; // slot disappeared or inaccessible — skip + } + + // Skip tokens that have not been initialised (no label/keys yet). + if ((tokenInfo.flags & CKF_TOKEN_INITIALIZED) == 0U) + { + continue; + } + + // Compare label (trimmed). + const std::string slotLabel = TrimTrailingSpaces(tokenInfo.label, sizeof(tokenInfo.label)); + if (slotLabel != tokenLabel) + { + continue; + } + + // Optionally compare model. + if (!tokenModel.empty()) + { + const std::string slotModel = TrimTrailingSpaces(tokenInfo.model, sizeof(tokenInfo.model)); + if (slotModel != tokenModel) + { + continue; + } + } + + return slots[i]; // match found + } + + // 3. No match. + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kQuotaExceeded); +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp b/score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp new file mode 100644 index 0000000..e83026d --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp @@ -0,0 +1,321 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_MODULE_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_MODULE_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +/// @brief Capability flags queried once at module initialisation. +struct Pkcs11Capabilities +{ + std::uint8_t versionMajor{0U}; + std::uint8_t versionMinor{0U}; + bool supportsMessageDigest{false}; ///< true if PKCS#11 v3.0+ C_MessageDigest* is available +}; + +/// @brief Session access type. +/// +/// ReadOnly: sufficient for digest, verify, sign, encrypt/decrypt, MAC, AEAD (reads keys, never creates). +/// ReadWrite: required for key generation, key import, persist, unwrap, delete — any write to token objects. +enum class Pkcs11SessionType : std::uint8_t +{ + ReadOnly = 0U, ///< CKF_SERIAL_SESSION — sufficient for all read and cryptographic ops + ReadWrite = 1U ///< CKF_SERIAL_SESSION | CKF_RW_SESSION — required for key creation/deletion/persistence +}; + +/// @brief Token-wide authentication state. +/// +/// PKCS#11 login is token-wide: C_Login on any session elevates ALL open sessions on the token. +/// SO is intentionally absent — the SCORE stack is always normal User. +enum class Pkcs11TokenAuthState : std::uint8_t +{ + Public = 0U, ///< No login — digest, verify, RNG, public cert ops + User = 1U ///< C_Login(CKU_USER) — sign, encrypt, decrypt, MAC, AEAD, all key mgmt ops +}; + +/// @brief Static session + auth requirements declared by each handler type. +/// +/// Used by Pkcs11HandlerFactory to call AcquireSession with correct parameters, +/// and by handlers to call ReleaseSession with correct accounting on destruction. +struct Pkcs11HandlerRequirements +{ + Pkcs11SessionType sessionType; ///< ReadOnly or ReadWrite (fixed at C_OpenSession) + Pkcs11TokenAuthState requiredAuth; ///< Public or User (token-wide state) +}; + +// Forward declaration — full definition follows SessionGuard below. +class Pkcs11Module; + +/// @brief Manages token-wide login state and reference-counts active User-authenticated handlers. +/// +/// PKCS#11 invariant: C_Login elevates ALL sessions on the token simultaneously. +/// C_Logout also reverts ALL sessions. This guard tracks how many handlers currently +/// require User state so logout is deferred until the last such handler is released. +/// +/// The user PIN is provided once at construction. An empty PIN is valid — +/// some PKCS#11 implementations (e.g. certain smart-card middleware) accept a +/// zero-length PIN for token login. +class TokenAuthGuard final +{ + public: + /// @brief Construct with the user PIN for this token. + /// @param pin User PIN. Empty string is valid (zero-length PIN login). + explicit TokenAuthGuard(std::string_view pin) noexcept; + + /// @brief Default-construct with no PIN (Public-only operations). + TokenAuthGuard() noexcept = default; + ~TokenAuthGuard() = default; + + TokenAuthGuard(const TokenAuthGuard&) = delete; + TokenAuthGuard& operator=(const TokenAuthGuard&) = delete; + TokenAuthGuard(TokenAuthGuard&&) = delete; + TokenAuthGuard& operator=(TokenAuthGuard&&) = delete; + + /// @brief Ensure token is in User state. No-op if already logged in. + /// @param anySession Any currently-open session on this token (login is token-wide). + /// @param module The owning module; its function list is used for C_Login. + /// @note Uses the PIN supplied at construction. + /// @note Increments the active-user-handler reference count on success. + [[nodiscard]] Expected EnsureUserState( + CK_SESSION_HANDLE anySession, + const Pkcs11Module& module) noexcept; + + /// @brief Notify that a handler which required User state has been destroyed. + /// + /// Always decrements the reference count and, when it reaches zero, reverts the + /// token state to Public. C_Logout is issued only when anySession is valid; + /// passing CK_INVALID_HANDLE is safe — the PKCS#11 spec guarantees the token + /// reverts to Public automatically when all its sessions are closed. + /// @param anySession Any currently-open session on this token, or CK_INVALID_HANDLE. + /// @param module The owning module; its function list is used for C_Logout. + void OnUserHandlerReleased(CK_SESSION_HANDLE anySession, const Pkcs11Module& module) noexcept; + + /// @brief Current token-wide auth state. + [[nodiscard]] Pkcs11TokenAuthState GetCurrentState() const noexcept; + + /// @brief Number of active handlers currently holding User-state sessions. + [[nodiscard]] std::uint32_t GetActiveUserHandlerCount() const noexcept; + + private: + mutable std::mutex m_mutex; ///< Protects m_state and m_activeUserCount against concurrent access + std::string m_pin; ///< User PIN for C_Login (empty is valid for some implementations) + Pkcs11TokenAuthState m_state{Pkcs11TokenAuthState::Public}; + std::uint32_t m_activeUserCount{0U}; +}; + +/// @brief Session cleanup strategy after handler destruction. +/// +/// Determines how thoroughly to clean session state when a handler is destroyed, +/// particularly important for interrupted/aborted streaming operations. +/// The correct choice depends on the PKCS#11 implementation's cleanup robustness. +enum class Pkcs11SessionCleanupStrategy : std::uint8_t +{ + /// Soft cleanup (default): Call C_DigestFinal (or equivalent) with dummy buffer. + /// + /// Complies with PKCS#11 spec v2.40+ — operation completes and session returns to IDLE. + /// Works reliably with modern implementations (SoftHSM 2.6+, Thales Luna, YubiHSM). + /// Internal session state varies by implementation but is functionally clean for next operation. + /// Pros: Fast, efficient, spec-compliant, minimal overhead + /// Cons: May leave residual data in implementations with incomplete cleanup + /// Reference: PKCS#11 v3.0, C_DigestFinal, etc. complete operations atomically. + kSoftCleanup = 0U, + + /// Hard cleanup: Close and reopen session with C_CloseSession + C_OpenSession. + /// + /// Guarantees complete session reset REGARDLESS of PKCS#11 implementation quality. + /// Useful for: Security-critical deployments, untrusted/legacy PKCS#11 implementations, + /// strict zero-state requirements, or hardware that exhibits cleanup bugs. + /// Pros: Most thorough, guaranteed fresh state, avoids residual state issues + /// Cons: Slower (two extra system calls), higher overhead, requires RW session to reopen + /// Note: Applied only during soft-cleanup failure or when explicitly required. + kHardCleanup = 1U +}; + +/// @brief Sentinel value for Pkcs11ProviderConfig::slotId indicating that the slot +/// should be auto-discovered by matching tokenLabel (and optionally tokenModel) +/// via C_GetSlotList + C_GetTokenInfo at Initialize() time. +inline constexpr CK_SLOT_ID kSlotIdAutoDetect{static_cast(CK_UNAVAILABLE_INFORMATION)}; + +/// @brief Configuration for a PKCS#11 provider instance (one token = one config). +struct Pkcs11ProviderConfig +{ + /// @brief Slot ID of the target token. + /// Set to kSlotIdAutoDetect to auto-discover the slot by tokenLabel/tokenModel. + CK_SLOT_ID slotId{kSlotIdAutoDetect}; + + /// @brief Human-readable token label (padded to 32 chars by PKCS#11). + /// Used for slot auto-discovery when slotId == kSlotIdAutoDetect. + std::string tokenLabel{}; + + /// @brief Optional token model string for disambiguation when multiple tokens + /// share the same label. Empty = match by label only. + std::string tokenModel{}; + + /// @brief User PIN for lazy C_Login on first User-state handler. + /// Empty string is valid — some PKCS#11 implementations accept zero-length PIN. + /// Leave empty if only Public operations are needed. + std::string userPin{}; + + std::string providerName{}; + std::uint32_t maxRoSessionsOverride{0U}; ///< 0 = read from C_GetTokenInfo.ulMaxSessionCount + std::uint32_t maxRwSessionsOverride{0U}; ///< 0 = read from C_GetTokenInfo.ulMaxRwSessionCount + Pkcs11SessionCleanupStrategy cleanupStrategy{Pkcs11SessionCleanupStrategy::kSoftCleanup}; + + /// @brief Optional C_Initialize arguments (thread-safety flags, mutex callbacks). + /// Nullptr = library-default behaviour. + CK_C_INITIALIZE_ARGS* initArgs{nullptr}; + + // sessionType intentionally removed — session type is now per-handler via Pkcs11HandlerRequirements + // soPin intentionally absent — SO role is never used in the SCORE stack +}; + +/// @brief RAII guard for C_Initialize / C_Finalize lifecycle. +/// +/// Owns C_Finalize ONLY if it itself called C_Initialize successfully (rv == CKR_OK). +/// If C_Initialize returns CKR_CRYPTOKI_ALREADY_INITIALIZED the guard does NOT take +/// ownership — it will not call C_Finalize on destruction. This prevents premature +/// library teardown when multiple Pkcs11Module instances exist. +/// For multi-provider deployments, share a single Pkcs11Module via std::shared_ptr. +class ModuleGuard final +{ + public: + ModuleGuard() noexcept; + ~ModuleGuard(); + + ModuleGuard(const ModuleGuard&) = delete; + ModuleGuard& operator=(const ModuleGuard&) = delete; + ModuleGuard(ModuleGuard&& other) noexcept; + ModuleGuard& operator=(ModuleGuard&& other) noexcept; + + /// @brief Calls C_Initialize with the given init args. + /// @param initArgs Optional CK_C_INITIALIZE_ARGS (thread-safety flags, mutex callbacks, etc.). + /// Pass nullptr for library-default behaviour (single-threaded / OS locking). + [[nodiscard]] Expected Initialize( + CK_C_INITIALIZE_ARGS* initArgs = nullptr) noexcept; + + /// @brief Returns true if C_Initialize succeeded. + [[nodiscard]] bool IsInitialized() const noexcept; + + private: + bool m_initialized; +}; + +/// @brief RAII guard for C_OpenSession / C_CloseSession lifecycle. +class SessionGuard final +{ + public: + SessionGuard() noexcept; + ~SessionGuard(); + + SessionGuard(const SessionGuard&) = delete; + SessionGuard& operator=(const SessionGuard&) = delete; + SessionGuard(SessionGuard&& other) noexcept; + SessionGuard& operator=(SessionGuard&& other) noexcept; + + /// @brief Opens a session on the given slot. + /// @param module The owning module; its function list is cached for Close(). + /// @param slotId The token slot to open a session on. + /// @param sessionType ReadOnly (digest, verify) or ReadWrite (key gen, object creation). + [[nodiscard]] Expected Open( + const Pkcs11Module& module, + CK_SLOT_ID slotId, + Pkcs11SessionType sessionType = Pkcs11SessionType::ReadOnly) noexcept; + + /// @brief Returns the managed session handle. Only valid when open. + [[nodiscard]] CK_SESSION_HANDLE Get() const noexcept; + + /// @brief Returns true if session is open. + [[nodiscard]] bool IsOpen() const noexcept; + + private: + void Close() noexcept; + CK_FUNCTION_LIST* + m_functionList; ///< cached from module.GetFunctionList() at Open(); outlives module during shutdown + CK_SESSION_HANDLE m_session; + bool m_open; +}; + +/// @brief Thin RAII wrapper around a linked PKCS#11 library's CK_FUNCTION_LIST. +/// +/// Obtained via C_GetFunctionList() from the already-linked library (no dlopen). +/// Queries CK_INFO.cryptokiVersion once at Init() to populate capability flags. +class Pkcs11Module final +{ + public: + Pkcs11Module() noexcept; + ~Pkcs11Module() = default; + + Pkcs11Module(const Pkcs11Module&) = delete; + Pkcs11Module& operator=(const Pkcs11Module&) = delete; + Pkcs11Module(Pkcs11Module&&) noexcept = default; + Pkcs11Module& operator=(Pkcs11Module&&) noexcept = default; + + /// @brief Obtains CK_FUNCTION_LIST, calls C_Initialize, queries version info. + /// @param initArgs Optional CK_C_INITIALIZE_ARGS forwarded to ModuleGuard::Initialize. + [[nodiscard]] Expected Init( + CK_C_INITIALIZE_ARGS* initArgs = nullptr) noexcept; + + /// @brief Auto-discover the slot ID for a token by label (and optionally model). + /// + /// Enumerates all slots with a token present via C_GetSlotList(CK_TRUE), + /// calls C_GetTokenInfo on each, and returns the first slot whose label + /// matches @p tokenLabel (space-trimmed comparison). If @p tokenModel is + /// non-empty, the model field must also match. + /// + /// Additionally verifies that CKF_TOKEN_INITIALIZED is set — uninitialised + /// tokens are skipped. + /// + /// @param module Initialised module whose function list is used for enumeration. + /// @param tokenLabel Required token label to match. + /// @param tokenModel Optional model string (empty = match label only). + /// @return Slot ID on success, or error (ERROR_RESOURCE_EXHAUSTED if no match). + [[nodiscard]] static Expected + FindSlotByToken(const Pkcs11Module& module, std::string_view tokenLabel, std::string_view tokenModel = {}) noexcept; + + /// @brief Returns the function list pointer. Only valid after successful Init(). + [[nodiscard]] CK_FUNCTION_LIST* GetFunctionList() const noexcept; + + /// @brief Returns capability flags queried at init time. + [[nodiscard]] const Pkcs11Capabilities& GetCapabilities() const noexcept; + + /// @brief Returns true if Init() has completed successfully. + /// Use this to avoid calling Init() on a shared module that was already initialised. + [[nodiscard]] bool IsInitialized() const noexcept; + + /// @brief Exhaustive mapping from CK_RV to score::crypto::daemon::common::DaemonErrorCode. + [[nodiscard]] static score::crypto::daemon::common::DaemonErrorCode MapErrorReturn(CK_RV rv) noexcept; + + private: + CK_FUNCTION_LIST* m_functionList; + ModuleGuard m_moduleGuard; + Pkcs11Capabilities m_capabilities; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_MODULE_HPP diff --git a/score/crypto/daemon/provider/pkcs11/pkcs11_provider.cpp b/score/crypto/daemon/provider/pkcs11/pkcs11_provider.cpp new file mode 100644 index 0000000..2f283fb --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_provider.cpp @@ -0,0 +1,423 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp" + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_factory.hpp" +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_slot_handler.hpp" +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.hpp" + +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +// ============================================================================ +// Construction +// ============================================================================ + +Pkcs11Provider::Pkcs11Provider(Pkcs11ProviderConfig config) + : m_config{std::move(config)}, + m_initialized{false}, + m_module{nullptr}, + m_roPool{}, + m_rwPool{}, + m_maxRoSessions{0U}, + m_maxRwSessions{0U}, + m_authGuard{m_config.userPin}, + m_handlerFactory{nullptr} +{ +} + +Pkcs11Provider::Pkcs11Provider(Pkcs11ProviderConfig config, std::shared_ptr sharedModule) + : m_config{std::move(config)}, + m_initialized{false}, + m_module{std::move(sharedModule)}, + m_roPool{}, + m_rwPool{}, + m_maxRoSessions{0U}, + m_maxRwSessions{0U}, + m_authGuard{m_config.userPin}, + m_handlerFactory{nullptr} +{ +} + +Pkcs11Provider::~Pkcs11Provider() +{ + Shutdown(); +} + +// ============================================================================ +// IProvider::Initialize +// ============================================================================ + +bool Pkcs11Provider::Initialize(const ProviderInitContext& ctx) +{ + if (m_initialized) + { + return true; + } + + m_numeric_id = ctx.numeric_id; + m_provider_name = ctx.name; + + if (!InitialiseLibrary() || !AutodiscoverSlot() || !QuerySessionLimits() || !SeedSessionPool()) + { + return false; + } + + // NOTE: m_handlerFactory is NOT created here anymore. It will be lazily created + // in GetCryptoHandlerFactory() after SetKeyManagementService() has been called. + // This avoids circular dependencies where the factory needs the service. + + m_initialized = true; + std::cout << "[PKCS#11] Provider (ID: " << m_numeric_id << ", Name: " << m_provider_name + << ") initialised successfully\n"; + return true; +} + +// ============================================================================ +// Initialize helpers +// ============================================================================ + +bool Pkcs11Provider::InitialiseLibrary() noexcept +{ + // Create an exclusive module instance when no shared one was injected. + if (m_module == nullptr) + { + m_module = std::make_shared(); + } + + // Shared modules may already be initialised by the caller (e.g. ProviderManager). + if (m_module->IsInitialized()) + { + return true; + } + + const auto result = m_module->Init(m_config.initArgs); + if (!result.has_value()) + { + std::cerr << "[PKCS#11] Error: Failed to initialise module (error " << static_cast(result.error()) + << ")\n"; + return false; + } + return true; +} + +bool Pkcs11Provider::AutodiscoverSlot() noexcept +{ + if (m_config.slotId != kSlotIdAutoDetect) + { + return true; // Slot already specified — nothing to do. + } + + if (m_config.tokenLabel.empty()) + { + std::cerr << "[PKCS#11] Error: slotId is kSlotIdAutoDetect but tokenLabel is empty\n"; + return false; + } + + const auto result = Pkcs11Module::FindSlotByToken(*m_module, m_config.tokenLabel, m_config.tokenModel); + if (!result.has_value()) + { + std::cerr << "[PKCS#11] Error: Failed to find slot for token '" << m_config.tokenLabel + << "' (error: " << static_cast(result.error()) << ")\n"; + return false; + } + + m_config.slotId = result.value(); + return true; +} + +bool Pkcs11Provider::QuerySessionLimits() noexcept +{ + // Two values indicate "no hard limit" in the PKCS#11 spec and must be treated as unlimited: + // 0x00000000 — PKCS#11 v2.40 CK_EFFECTIVELY_INFINITE (SoftHSM uses this) + // 0xFFFFFFFF — PKCS#11 v3.0 CK_UNAVAILABLE_INFORMATION + // In both cases cap at a safe default so the pool has a finite upper bound. + constexpr CK_ULONG kNoLimit{0xFFFFFFFFUL}; + constexpr CK_ULONG kEffectivelyInfinite{0U}; + constexpr CK_ULONG kDefaultMaxSessions{32UL}; + + CK_TOKEN_INFO tokenInfo{}; + const CK_RV rv = m_module->GetFunctionList()->C_GetTokenInfo(m_config.slotId, &tokenInfo); + if (rv != CKR_OK) + { + std::cerr << "[PKCS#11] Error: C_GetTokenInfo failed on slot " << m_config.slotId + << " (rv=" << static_cast(rv) << ")\n"; + return false; + } + + const auto resolveLimit = [&](CK_ULONG tokenVal, std::uint32_t override_) -> CK_ULONG { + if (override_ != 0U) + { + return static_cast(override_); + } + const bool unlimited = (tokenVal == kNoLimit) || (tokenVal == kEffectivelyInfinite); + return unlimited ? kDefaultMaxSessions : tokenVal; + }; + + m_maxRoSessions = resolveLimit(tokenInfo.ulMaxSessionCount, m_config.maxRoSessionsOverride); + m_maxRwSessions = resolveLimit(tokenInfo.ulMaxRwSessionCount, m_config.maxRwSessionsOverride); + return true; +} + +bool Pkcs11Provider::SeedSessionPool() noexcept +{ + // Open one initial ReadOnly session to validate slot accessibility and seed the pool. + auto seedSession = std::make_unique(); + const auto result = seedSession->Open(*m_module, m_config.slotId, Pkcs11SessionType::ReadOnly); + if (!result.has_value()) + { + std::cerr << "[PKCS#11] Error: Failed to open initial session on slot " << m_config.slotId << " (error " + << static_cast(result.error()) << ")\n"; + return false; + } + m_roPool.push_back(PooledSession{std::move(seedSession), false}); + return true; +} + +// ============================================================================ +// IProvider::Shutdown +// ============================================================================ + +void Pkcs11Provider::Shutdown() +{ + if (!m_initialized) + { + return; + } + + m_handlerFactory.reset(); + + // Close all pooled sessions (RAII via SessionGuard destructors). + m_roPool.clear(); + m_rwPool.clear(); + + m_module.reset(); // decrement shared refcount; C_Finalize only when last owner + m_initialized = false; +} + +// ============================================================================ +// Session pool helpers +// ============================================================================ + +CK_SESSION_HANDLE Pkcs11Provider::AnyOpenSession() const noexcept +{ + for (const auto& entry : m_roPool) + { + if (entry.guard && entry.guard->IsOpen()) + { + return entry.guard->Get(); + } + } + for (const auto& entry : m_rwPool) + { + if (entry.guard && entry.guard->IsOpen()) + { + return entry.guard->Get(); + } + } + return CK_INVALID_HANDLE; +} + +Expected Pkcs11Provider::AcquireFromPool( + std::vector& pool, + const Pkcs11SessionType sessionType, + const CK_ULONG hardLimit) noexcept +{ + // Try to reuse an idle session. + for (auto& entry : pool) + { + if (!entry.inUse) + { + entry.inUse = true; + return entry.guard->Get(); + } + } + + // No idle session -- check hard limit. + if (static_cast(pool.size()) >= hardLimit) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kQuotaExceeded); + } + + // Open a new session. + auto newSession = std::make_unique(); + const auto openResult = newSession->Open(*m_module, m_config.slotId, sessionType); + if (!openResult.has_value()) + { + return make_unexpected(openResult.error()); + } + + const CK_SESSION_HANDLE handle = newSession->Get(); + pool.push_back(PooledSession{std::move(newSession), true}); + return handle; +} + +// ============================================================================ +// AcquireSession / ReleaseSession +// ============================================================================ + +Expected Pkcs11Provider::AcquireSession( + const Pkcs11HandlerRequirements& requirements) noexcept +{ + std::lock_guard lock(m_poolMutex); + auto& pool = (requirements.sessionType == Pkcs11SessionType::ReadWrite) ? m_rwPool : m_roPool; + const CK_ULONG limit = + (requirements.sessionType == Pkcs11SessionType::ReadWrite) ? m_maxRwSessions : m_maxRoSessions; + + auto sessionResult = AcquireFromPool(pool, requirements.sessionType, limit); + if (!sessionResult.has_value()) + { + return sessionResult; + } + + // Lazy login: elevate to User state if required and not already logged in. + if (requirements.requiredAuth == Pkcs11TokenAuthState::User) + { + const auto loginResult = m_authGuard.EnsureUserState(sessionResult.value(), *m_module); + if (!loginResult.has_value()) + { + // Return session to pool on login failure. + for (auto& entry : pool) + { + if (entry.guard && entry.guard->Get() == sessionResult.value()) + { + entry.inUse = false; + break; + } + } + return make_unexpected(loginResult.error()); + } + } + + return sessionResult; +} + +void Pkcs11Provider::ReleaseSession(const CK_SESSION_HANDLE session, + const Pkcs11HandlerRequirements& usedRequirements) noexcept +{ + std::lock_guard lock(m_poolMutex); + auto& pool = (usedRequirements.sessionType == Pkcs11SessionType::ReadWrite) ? m_rwPool : m_roPool; + + auto it = pool.end(); + for (auto candidate = pool.begin(); candidate != pool.end(); ++candidate) + { + if (candidate->guard && (candidate->guard->Get() == session)) + { + it = candidate; + break; + } + } + + // Notify auth guard while session is still open — OnUserHandlerReleased may + // call C_Logout, which requires a valid handle. + if (usedRequirements.requiredAuth == Pkcs11TokenAuthState::User) + { + m_authGuard.OnUserHandlerReleased(session, *m_module); + } + + if (it != pool.end()) + { + if (m_config.cleanupStrategy == Pkcs11SessionCleanupStrategy::kHardCleanup) + { + // Erase the entry: unique_ptr destructor calls Close(), + // which calls C_CloseSession. No closed-but-idle slots are left in + // the pool; AcquireFromPool opens a fresh session when next needed. + pool.erase(it); + } + else + { + it->inUse = false; + } + } +} + +// ============================================================================ +// Session validation +// ============================================================================ + +bool Pkcs11Provider::ValidateSession(const CK_SESSION_HANDLE session) const noexcept +{ + if ((session == CK_INVALID_HANDLE) || (m_module == nullptr)) + { + return false; + } + CK_FUNCTION_LIST* const fns = m_module->GetFunctionList(); + if (fns == nullptr) + { + return false; + } + CK_SESSION_INFO info{}; + const CK_RV rv = fns->C_GetSessionInfo(session, &info); + return (rv == CKR_OK); +} + +// ============================================================================ +// IProvider -- other interface methods +// ============================================================================ + +common::ProviderId Pkcs11Provider::GetProviderId() const +{ + return m_numeric_id; +} + +const common::ProviderName& Pkcs11Provider::GetProviderName() const +{ + return m_provider_name; +} + +handler::ICryptoHandlerFactory::Sptr Pkcs11Provider::GetCryptoHandlerFactory() +{ + if (!m_handlerFactory) + { + // Lazy creation: by the time this is called, SetKeyManagementService() has been + // called by KeyManagementModule::Create(), so the service is guaranteed to be available. + m_handlerFactory = std::make_shared(*m_module, *this); + } + return m_handlerFactory; +} + +std::shared_ptr Pkcs11Provider::GetKeyFactory() +{ + if (!m_key_factory) + { + // Lazy initialization: ensure key store exists first + if (!m_key_store) + { + m_key_store = std::make_shared(shared_from_this(), m_module); + } + m_key_factory = std::make_shared(shared_from_this(), m_module, m_key_store); + } + return m_key_factory; +} + +std::shared_ptr Pkcs11Provider::GetKeySlotHandler( + const key_management::KeySlotConfig& /*config*/) +{ + // Transient: a new Pkcs11KeySlotHandler is created per call and destroyed after + // the operation. The shared Pkcs11KeyStore (opaque-id table) is retained + // via m_key_store so PKCS#11 object handles remain valid across calls. + + // Ensure key store exists + if (!m_key_store) + { + m_key_store = std::make_shared(shared_from_this(), m_module); + } + return std::make_shared(shared_from_this(), m_module, m_key_store); +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp b/score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp new file mode 100644 index 0000000..d4241bf --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp @@ -0,0 +1,197 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_PROVIDER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_PROVIDER_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/key_management/core/key_management_service.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_factory.hpp" +#include "score/crypto/daemon/provider/i_provider.hpp" +#include "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/factory/pkcs11_handler_factory.hpp" +#include "score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" +#include +#include + +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +// Forward declarations +class Pkcs11KeyStore; +class Pkcs11KeyFactory; +class Pkcs11KeyManagementHandler; + +/// @brief PKCS#11 provider -- one instance per token/slot. +/// +/// Supports crypto operations (hash, MAC via PKCS#11 C_Digest*/C_Sign*) and +/// key management (key generation, import, slot loading via PKCS#11 +/// C_GenerateKey / C_CreateObject / C_FindObjects). +/// +/// Owns a shared Pkcs11Module (library lifecycle), a token-wide TokenAuthGuard, +/// and two session pools: +/// - m_roPool: ReadOnly sessions for hash, verify, sign, encrypt, decrypt, MAC, AEAD +/// - m_rwPool: ReadWrite sessions for key generation, persistence, import, deletion +/// +/// Sessions are acquired per-handler (one active PKCS#11 operation per session) +/// and returned to the pool on handler destruction. Login/logout is deferred: +/// C_Login is called on the first User-state handler acquisition; C_Logout is called +/// when the last User-state handler is released. +class Pkcs11Provider final : public IProvider, public std::enable_shared_from_this +{ + public: + /// @brief Construct provider with an exclusive PKCS#11 module (single-provider path). + explicit Pkcs11Provider(Pkcs11ProviderConfig config); + + /// @brief Construct provider with a shared, pre-initialized PKCS#11 module. + /// + /// Use this constructor when multiple token-bound providers share the same linked + /// PKCS#11 library. The caller (e.g. ProviderManager) must call sharedModule->Init() + /// before constructing any provider with it. C_Finalize is deferred until the last + /// shared_ptr owner is destroyed -- guaranteeing all sessions are closed first. + Pkcs11Provider(Pkcs11ProviderConfig config, std::shared_ptr sharedModule); + + ~Pkcs11Provider() override; + + Pkcs11Provider(const Pkcs11Provider&) = delete; + Pkcs11Provider& operator=(const Pkcs11Provider&) = delete; + Pkcs11Provider(Pkcs11Provider&&) = delete; + Pkcs11Provider& operator=(Pkcs11Provider&&) = delete; + + // --- IProvider interface --- + + [[nodiscard]] bool Initialize(const ProviderInitContext& ctx) override; + void Shutdown() override; + [[nodiscard]] common::ProviderId GetProviderId() const override; + [[nodiscard]] const common::ProviderName& GetProviderName() const override; + + // --- Crypto capability --- + + [[nodiscard]] std::shared_ptr GetCryptoHandlerFactory() override; + + // --- Key management capability --- + + /// Return the PKCS#11 key factory. + [[nodiscard]] std::shared_ptr GetKeyFactory() override; + + /// @brief Inject the key management service for key DataNode lifecycle management. + void SetKeyManagementService(std::shared_ptr service) override + { + m_keyManagementService = std::move(service); + } + + /// @brief Get the injected key management service (may be null if not yet injected). + [[nodiscard]] std::shared_ptr GetKeyManagementService() const + { + return m_keyManagementService; + } + + /// @brief Return a key slot handler for the given slot configuration. + /// + /// Returns a new Pkcs11KeySlotHandler per call. The shared Pkcs11KeyHandler + /// (key map + session pool) is retained via m_key_handler so PKCS#11 object + /// handles remain valid across calls. + [[nodiscard]] std::shared_ptr GetKeySlotHandler( + const key_management::KeySlotConfig& config) override; + + // --- Session pool API (called by handlers via factory) --- + + /// @brief Acquire a session for an operation handler. + /// + /// 1. If requiredAuth==User and token is Public -> lazy C_Login. + /// 2. Reuse an idle session from the appropriate pool (RO or RW). + /// 3. Open a new session if no idle slot and pool limit not reached. + /// 4. Return ERROR_RESOURCE_EXHAUSTED if pool is at the hard limit. + [[nodiscard]] Expected AcquireSession( + const Pkcs11HandlerRequirements& requirements) noexcept; + + /// @brief Return a session from a handler being destroyed. + /// + /// 1. Mark session slot idle in the appropriate pool. + /// 2. If usedAuth==User -> decrement active-user-handler count. + /// 3. If count reaches zero -> C_Logout (token reverts to Public). + void ReleaseSession(CK_SESSION_HANDLE session, const Pkcs11HandlerRequirements& usedRequirements) noexcept; + + /// @brief Validate that a session handle is still usable. + /// + /// Calls C_GetSessionInfo to verify the session has not been closed or + /// invalidated (e.g. after token removal or HSM error). + /// @return true if the session is valid and open, false otherwise. + [[nodiscard]] bool ValidateSession(CK_SESSION_HANDLE session) const noexcept; + + private: + /// @brief A pooled session entry. + struct PooledSession + { + std::unique_ptr guard; + bool inUse{false}; + }; + + /// @brief Try to acquire from pool; open new session if no idle entry and under limit. + [[nodiscard]] Expected + AcquireFromPool(std::vector& pool, Pkcs11SessionType sessionType, CK_ULONG hardLimit) noexcept; + + /// @brief Return any open session handle for token-wide operations (login/logout). + /// Returns CK_INVALID_HANDLE if no session is open at all. + [[nodiscard]] CK_SESSION_HANDLE AnyOpenSession() const noexcept; + + // --- Initialize helpers (called in order by Initialize()) --- + + /// @brief Ensure m_module is created and C_Initialize has been called. + /// @return false and logs on failure. + [[nodiscard]] bool InitialiseLibrary() noexcept; + + /// @brief Resolve the concrete slot ID when slotId == kSlotIdAutoDetect. + /// @return false and logs on failure. + [[nodiscard]] bool AutodiscoverSlot() noexcept; + + /// @brief Query C_GetTokenInfo and populate m_maxRoSessions / m_maxRwSessions. + /// @return false and logs on failure. + [[nodiscard]] bool QuerySessionLimits() noexcept; + + /// @brief Open the initial ReadOnly seed session and push it onto m_roPool. + /// @return false and logs on failure. + [[nodiscard]] bool SeedSessionPool() noexcept; + + Pkcs11ProviderConfig m_config; + bool m_initialized{false}; + common::ProviderId m_numeric_id{common::kInvalidProviderId}; + common::ProviderName m_provider_name{}; + std::shared_ptr m_module; ///< shared across all providers on the same library + + std::vector m_roPool; ///< ReadOnly sessions + std::vector m_rwPool; ///< ReadWrite sessions + CK_ULONG m_maxRoSessions{0U}; ///< from C_GetTokenInfo.ulMaxSessionCount + CK_ULONG m_maxRwSessions{0U}; ///< from C_GetTokenInfo.ulMaxRwSessionCount + + mutable std::mutex m_poolMutex; ///< Protects m_roPool, m_rwPool against concurrent access + TokenAuthGuard m_authGuard; ///< token-wide login state + User refcount + handler::ICryptoHandlerFactory::Sptr m_handlerFactory; + + /// @brief Key store: opaque-id ↔ (session, object) translation table. + std::shared_ptr m_key_store; + /// @brief Key factory: GenerateKey and ImportKey implementation. + std::shared_ptr m_key_factory; + key_management::KeyManagementService::Sptr m_keyManagementService; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_PROVIDER_HPP diff --git a/score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.cpp b/score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.cpp new file mode 100644 index 0000000..9a7a57b --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.cpp @@ -0,0 +1,65 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.hpp" + +#include + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" + +namespace score::crypto::daemon::provider::pkcs11 +{ + +Pkcs11ProviderFactory::Pkcs11ProviderFactory(std::vector configs) + : m_injected_configs{std::move(configs)} +{ +} + +void Pkcs11ProviderFactory::SetTokenConfigs(std::vector configs) +{ + m_injected_configs = std::move(configs); +} + +bool Pkcs11ProviderFactory::CreateAndRegister(ProviderManager& manager) +{ + if (m_injected_configs.empty()) + { + return true; + } + + // All tokens on the same linked library MUST share a single Pkcs11Module + // so that C_Initialize is called exactly once and C_Finalize is deferred + // until the very last provider (and therefore all its sessions) is destroyed. + auto pkcs11Module = std::make_shared(); + const auto initResult = pkcs11Module->Init(); + if (!initResult.has_value()) + { + return false; + } + + for (const auto& config : m_injected_configs) + { + auto provider = std::make_shared(config, pkcs11Module); + if (!manager.RegisterProvider(config.providerName, provider, common::CryptoProviderType::HARDWARE)) + { + return false; + } + } + + return true; +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.hpp b/score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.hpp new file mode 100644 index 0000000..f17d22f --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.hpp @@ -0,0 +1,84 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_PROVIDER_FACTORY_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_PROVIDER_FACTORY_HPP + +#include "score/crypto/daemon/provider/i_provider_factory.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" + +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +/** + * @brief Factory that creates and registers PKCS#11 token providers. + * + * Token configuration is supplied externally via SetTokenConfigs() (the acceptor + * side of the Pkcs11Config visitor pattern) or the explicit vector constructor. + * The daemon bootstrapper delegates config setup to Pkcs11Config::Configure(): + * + * @code + * config.GetPkcs11Config().PopulateDefaults(); + * auto factory = std::make_unique(); + * config.GetPkcs11Config().Configure(*factory); + * manager.RegisterFactory(std::move(factory)); + * @endcode + * + * All configured tokens share a single Pkcs11Module so that C_Initialize is + * called only once for the linked PKCS#11 library, regardless of how many + * token-bound providers are registered. + */ +class Pkcs11ProviderFactory final : public IProviderFactory +{ + public: + /// Construct with default (empty) token configuration. + Pkcs11ProviderFactory() = default; + + /// Construct with externally supplied token configurations. + /// + /// Called by Pkcs11Config::Configure() via SetTokenConfigs(), or directly + /// in tests that need to inject specific PKCS#11 provider configs. + explicit Pkcs11ProviderFactory(std::vector configs); + + /// @brief Accept a token-config vector pushed by Pkcs11Config::Configure(). + /// + /// This is the "acceptor" side of the visitor pattern: Pkcs11Config + /// (the visitor) converts its Pkcs11TokenEntry list to Pkcs11ProviderConfigs + /// and hands them to the factory via this method. + void SetTokenConfigs(std::vector configs); + + ~Pkcs11ProviderFactory() override = default; + + /** + * @brief Initialise a shared Pkcs11Module (C_Initialize called once), then + * construct and register one Pkcs11Provider per configured token as + * CryptoProviderType::HARDWARE. + * + * Returns true if module initialisation and all registrations succeeded. + * Returns false on the first failure without partial registration. + * Returns true immediately (no-op) when no token configs were injected. + * + * @param manager The ProviderManager to register providers into. + * @return true on full success. + */ + bool CreateAndRegister(ProviderManager& manager) override; + + private: + /// Token configurations injected at construction (empty = no providers registered). + std::vector m_injected_configs; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_PROVIDER_FACTORY_HPP diff --git a/score/crypto/daemon/provider/pkcs11/pkcs11_session_guard.hpp b/score/crypto/daemon/provider/pkcs11/pkcs11_session_guard.hpp new file mode 100644 index 0000000..2d05ca7 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_session_guard.hpp @@ -0,0 +1,138 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_DAEMON_PROVIDER_PKCS11_SESSION_GUARD_HPP +#define CRYPTO_DAEMON_PROVIDER_PKCS11_SESSION_GUARD_HPP + +#include "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp" + +namespace score::crypto::daemon::provider::pkcs11 +{ + +/// @brief RAII guard for pool-level PKCS#11 session acquire/release. +/// +/// Wraps `Pkcs11Provider::AcquireSession` / `ReleaseSession` to guarantee that +/// every acquired session is returned to the pool on all code paths (including +/// exceptions and early returns). This eliminates the fragile manual +/// release-on-error pattern in key factories and handler constructors. +/// +/// ### Usage +/// ```cpp +/// Pkcs11SessionGuard guard(provider, requirements); +/// if (!guard) { return make_unexpected(guard.error()); } +/// CK_SESSION_HANDLE session = guard.get(); +/// // ... use session ... +/// // on scope exit, guard releases the session back to the pool +/// ``` +/// +/// ### Ownership transfer +/// Call `release()` to yield ownership of the session handle (e.g. when the +/// session must outlive the guard, such as storing it in a handler instance). +/// After `release()`, the guard no longer calls `ReleaseSession`. +class Pkcs11SessionGuard final +{ + public: + /// @brief Acquire a session from the provider pool. + Pkcs11SessionGuard(Pkcs11Provider& provider, const Pkcs11HandlerRequirements& requirements) noexcept + : m_provider{&provider}, m_requirements{requirements} + { + auto result = provider.AcquireSession(requirements); + if (result.has_value()) + { + m_session = result.value(); + } + else + { + m_error = result.error(); + } + } + + ~Pkcs11SessionGuard() + { + if (m_session != CK_INVALID_HANDLE && m_provider != nullptr) + { + m_provider->ReleaseSession(m_session, m_requirements); + } + } + + Pkcs11SessionGuard(const Pkcs11SessionGuard&) = delete; + Pkcs11SessionGuard& operator=(const Pkcs11SessionGuard&) = delete; + + Pkcs11SessionGuard(Pkcs11SessionGuard&& other) noexcept + : m_provider{other.m_provider}, + m_session{other.m_session}, + m_requirements{other.m_requirements}, + m_error{other.m_error} + { + other.m_session = CK_INVALID_HANDLE; + other.m_provider = nullptr; + } + + Pkcs11SessionGuard& operator=(Pkcs11SessionGuard&& other) noexcept + { + if (this != &other) + { + if (m_session != CK_INVALID_HANDLE && m_provider != nullptr) + { + m_provider->ReleaseSession(m_session, m_requirements); + } + m_provider = other.m_provider; + m_session = other.m_session; + m_requirements = other.m_requirements; + m_error = other.m_error; + other.m_session = CK_INVALID_HANDLE; + other.m_provider = nullptr; + } + return *this; + } + + /// @brief Check if the session was acquired successfully. + [[nodiscard]] explicit operator bool() const noexcept + { + return m_session != CK_INVALID_HANDLE; + } + + /// @brief Get the acquired session handle. + [[nodiscard]] CK_SESSION_HANDLE get() const noexcept + { + return m_session; + } + + /// @brief Get the acquisition error (valid only when `!guard`). + [[nodiscard]] score::crypto::daemon::common::DaemonErrorCode error() const noexcept + { + return m_error; + } + + /// @brief Yield ownership of the session handle. + /// + /// After this call the guard will NOT release the session on destruction. + /// The caller assumes responsibility for calling `ReleaseSession`. + [[nodiscard]] CK_SESSION_HANDLE release() noexcept + { + CK_SESSION_HANDLE h = m_session; + m_session = CK_INVALID_HANDLE; + m_provider = nullptr; + return h; + } + + private: + Pkcs11Provider* m_provider{nullptr}; + CK_SESSION_HANDLE m_session{CK_INVALID_HANDLE}; + Pkcs11HandlerRequirements m_requirements{}; + score::crypto::daemon::common::DaemonErrorCode m_error{score::crypto::daemon::common::DaemonErrorCode::kUnknown}; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // CRYPTO_DAEMON_PROVIDER_PKCS11_SESSION_GUARD_HPP diff --git a/score/crypto/daemon/provider/pkcs11/pkcs11_token_config.cpp b/score/crypto/daemon/provider/pkcs11/pkcs11_token_config.cpp new file mode 100644 index 0000000..5e6cf3b --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_token_config.cpp @@ -0,0 +1,55 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/pkcs11/pkcs11_token_config.hpp" + +// pkcs11_module.hpp brings in Pkcs11ProviderConfig + Pkcs11SessionCleanupStrategy; +// pkcs11_provider_factory.hpp brings in SetTokenConfigs() and the full class definition. +#include "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.hpp" + +namespace score::crypto::daemon::provider::pkcs11 +{ + +void Pkcs11Config::PopulateDefaults() +{ + if (!m_tokens.empty()) + { + return; // Entries already present (from config file or test fixture). + } + Pkcs11TokenEntry softHsm{}; + softHsm.tokenLabel = "SoftHSM"; + softHsm.userPin = "1234"; + softHsm.providerName = "SOFTHSM"; + softHsm.useHardCleanup = true; + m_tokens.push_back(std::move(softHsm)); +} + +void Pkcs11Config::Configure(Pkcs11ProviderFactory& factory) const +{ + std::vector configs; + configs.reserve(m_tokens.size()); + for (const auto& entry : m_tokens) + { + Pkcs11ProviderConfig cfg{}; + cfg.tokenLabel = entry.tokenLabel; + cfg.tokenModel = entry.tokenModel; + cfg.userPin = entry.userPin; + cfg.providerName = entry.providerName; + cfg.cleanupStrategy = entry.useHardCleanup ? Pkcs11SessionCleanupStrategy::kHardCleanup + : Pkcs11SessionCleanupStrategy::kSoftCleanup; + configs.push_back(std::move(cfg)); + } + factory.SetTokenConfigs(std::move(configs)); +} + +} // namespace score::crypto::daemon::provider::pkcs11 diff --git a/score/crypto/daemon/provider/pkcs11/pkcs11_token_config.hpp b/score/crypto/daemon/provider/pkcs11/pkcs11_token_config.hpp new file mode 100644 index 0000000..a96e3b7 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_token_config.hpp @@ -0,0 +1,99 @@ +// ============================================================================= +// C O P Y R I G H T +// ============================================================================= +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_TOKEN_CONFIG_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_TOKEN_CONFIG_HPP + +#include +#include + +namespace score::crypto::daemon::provider::pkcs11 +{ + +// Forward declaration — Configure() is implemented in pkcs11_token_config.cpp +// to keep this header free of PKCS#11 C API types (pkcs11_module.hpp). +class Pkcs11ProviderFactory; + +/// @brief Plain-data configuration entry for one PKCS#11 token. +/// +/// All fields are standard-library types so this struct is usable by both the +/// generic daemon config reader (JSON / flatbuffer) and the bootstrapper +/// without pulling in any PKCS#11 C headers. +struct Pkcs11TokenEntry +{ + /// Human-readable token label used for slot auto-discovery. + std::string tokenLabel{}; + /// Optional model string for disambiguation when multiple tokens share the same label. + std::string tokenModel{}; + /// User PIN for C_Login on first privileged session. Empty = no login needed. + std::string userPin{}; + /// Provider name used to register and look up this provider in ProviderManager. + std::string providerName{}; + /// true = kHardCleanup (re-open session after every handler), false = kSoftCleanup. + bool useHardCleanup{true}; +}; + +/// @brief Aggregates the ordered list of PKCS#11 token entries for the daemon. +/// +/// This is the canonical PKCS#11-specific configuration type. The daemon's +/// top-level Config class holds one instance (via a type alias in the config +/// namespace) so that config.hpp does not need to define PKCS#11 structures. +/// +/// @par Visitor pattern +/// Configure() acts as a "visitor" that pushes the token entries into a +/// Pkcs11ProviderFactory. The conversion from Pkcs11TokenEntry to +/// Pkcs11ProviderConfig lives entirely within the PKCS#11 subsystem. +/// Typical bootstrapper usage: +/// @code +/// config.GetPkcs11Config().PopulateDefaults(); +/// auto factory = std::make_unique(); +/// config.GetPkcs11Config().Configure(*factory); +/// manager.RegisterFactory(std::move(factory)); +/// @endcode +class Pkcs11Config +{ + public: + Pkcs11Config() = default; + + /// @brief Add a token entry (called by parser or bootstrapper). + void AddTokenEntry(Pkcs11TokenEntry entry) + { + m_tokens.push_back(std::move(entry)); + } + + /// @brief Get all registered token entries (read-only). + const std::vector& GetTokenEntries() const + { + return m_tokens; + } + + /// @brief Populate production default token entries when no config was loaded. + /// + /// Adds a SoftHSM entry with standard test credentials. No-op if any + /// token entries are already present (e.g. loaded from file or test fixture). + void PopulateDefaults(); + + /// @brief Visit @p factory: convert each token entry and configure the factory. + /// + /// Converts each Pkcs11TokenEntry to a Pkcs11ProviderConfig and calls + /// factory.SetTokenConfigs(). Implemented out-of-line in + /// pkcs11_token_config.cpp so that this header remains free of + /// PKCS#11 C API types. + void Configure(Pkcs11ProviderFactory& factory) const; + + private: + std::vector m_tokens; +}; + +} // namespace score::crypto::daemon::provider::pkcs11 + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_PKCS11_TOKEN_CONFIG_HPP diff --git a/score/crypto/daemon/provider/provider_manager.hpp b/score/crypto/daemon/provider/provider_manager.hpp new file mode 100644 index 0000000..3e5c5d9 --- /dev/null +++ b/score/crypto/daemon/provider/provider_manager.hpp @@ -0,0 +1,284 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_MANAGER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_MANAGER_HPP + +#include +#include +#include +#include +#include +#include + +#include "i_provider.hpp" +#include "i_provider_factory.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/config/inc/config.hpp" + +namespace score::crypto +{ +namespace daemon +{ +namespace provider +{ + +/** + * @brief Provider entry point - represents a registered provider instance + * + * Stores both the human-readable name (from configuration) and the assigned + * numeric ID (from ProviderManager at registration time). + */ +struct ProviderEntry +{ + common::ProviderName name; ///< Human-readable name from config/factory + common::ProviderId numeric_id; ///< Index assigned at registration + std::shared_ptr instance; ///< Provider instance + common::CryptoProviderType cryptoType; ///< Functional category + + // Default constructor + ProviderEntry() + : name(""), + numeric_id(common::kInvalidProviderId), + instance(nullptr), + cryptoType(common::CryptoProviderType::DEFAULT) + { + } + + ProviderEntry(const common::ProviderName& providerName, + common::ProviderId id, + std::shared_ptr prov, + common::CryptoProviderType cryptoType) + : name(providerName), numeric_id(id), instance(prov), cryptoType(cryptoType) + { + } +}; + +/** + * @brief Initialization class for managing crypto provider instances + * + * This class manages the lifecycle of provider instances during daemon startup. + * The daemon calls Initialize() once during startup with a configuration. + * + * To add new providers: + * 1. Modify the Initialize() or CreateProviders() implementation in + * provider_manager.cpp + * 2. Add your provider instantiation logic + * 3. The factory will register and manage your provider automatically + * + * Usage Pattern: + * In daemon main: + * ProviderManager manager; + * ProviderInitConfig config; + * config.SetDefaultProviderForType(CryptoProviderType::SOFTWARE, + * kProviderNameOpenSSL); manager.Initialize(config); auto provider = + * manager.GetProvider(kProviderNameOpenSSL); + */ +class ProviderManager +{ + public: + using Sptr = std::shared_ptr; + /** + * @brief Constructor + */ + ProviderManager(const score::crypto::daemon::config::Config& config); + + /** + * @brief Destructor - cleans up all registered providers + */ + ~ProviderManager(); + + // Disable copy operations + ProviderManager(const ProviderManager&) = delete; + ProviderManager& operator=(const ProviderManager&) = delete; + + // Allow move operations + ProviderManager(ProviderManager&&) noexcept = delete; + ProviderManager& operator=(ProviderManager&&) noexcept = delete; + + /** + * @brief Initialize the factory with provider configuration + * + * This method initializes all providers based on the provided configuration. + * If no config is provided, a default configuration is used that: + * - Enables all available providers + * - Sets the first available provider as default for all applicable types + * + * This is the only method the daemon needs to call during startup. + * Provider instantiation logic is in the implementation file. + * + * @param config Optional provider initialization configuration. If not + * provided, a default configuration is created automatically. + * @return true if all providers initialized successfully, false otherwise + * @throws std::runtime_error if provider initialization fails + */ + bool Initialize(); + + /** + * @brief Get a provider by its numeric ID + * + * @param providerId The numeric provider identifier (uint16_t) + * @return Shared pointer to the provider, or nullptr if not found + */ + std::shared_ptr GetProvider(common::ProviderId providerId) const; + + /** + * @brief Get a provider by its name string + * + * @param providerName The human-readable provider name + * @return Shared pointer to the provider, or nullptr if not found + */ + std::shared_ptr GetProvider(const common::ProviderName& providerName) const; + + /** + * @brief Get the default provider for a specific crypto provider type + * + * @param cryptoType The functional category + * @return Shared pointer to the provider for this type, or nullptr if not + * found + */ + std::shared_ptr GetProvider(common::CryptoProviderType cryptoType) const; + + /** + * @brief Set a provider as default for a specific crypto provider type + * + * This allows configuring the same provider to serve as the default + * for different CryptoProviderType categories. + * + * @param cryptoType The functional category + * @param providerId The numeric provider ID to set as default for this type + * @return true if successful, false if provider ID not found or type mismatch + */ + bool SetDefaultProviderForType(common::CryptoProviderType cryptoType, common::ProviderId providerId); + + /** + * @brief Shutdown all registered providers + */ + void Shutdown(); + + /** + * @brief Register a provider in the factory registry. + * + * Called by IProviderFactory implementations during CreateAndRegister(). + * Automatically assigns a numeric ID (0, 1, 2, ...) in registration order. + * + * @param providerName Human-readable name for the provider + * @param provider Shared pointer to the provider instance + * @param cryptoType Functional category of provider + * @return true if provider registered successfully, false if name already exists + */ + bool RegisterProvider(const common::ProviderName& providerName, + std::shared_ptr provider, + common::CryptoProviderType cryptoType); + + /** + * @brief Register a provider factory to be invoked during Initialize(). + * + * Factories are called in registration order inside Initialize(), before + * provider configuration is applied. Ownership is transferred to the + * ProviderManager. + * + * @param factory Concrete factory instance (must be non-null). + */ + void RegisterFactory(std::unique_ptr factory); + + /** + * @brief Invoke a callback for each registered provider. + * + * @param fn Callable taking a const common::ProviderName& and a shared_ptr. + */ + template + void ForEachProvider(Fn&& fn) const + { + for (const auto& entry : m_providers) + { + fn(entry.first, entry.second.instance); + } + } + + /** + * @brief Look up the CryptoProviderType registered for a given provider by name. + * + * @param provider_name The provider's human-readable name. + * @return The provider's type, or std::nullopt if the name is not registered. + */ + [[nodiscard]] std::optional GetProviderType( + const common::ProviderName& provider_name) const; + + /** + * @brief Check whether a provider's type is compatible with a requested type. + * + * Compatibility rules: + * DEFAULT — matches any provider. + * HARDWARE — matches only HARDWARE providers. + * SOFTWARE — matches only SOFTWARE providers. + * + * @param provider_name Registered provider name to check. + * @param requested_type The caller's type preference. + * @return true if the provider satisfies the type constraint, false otherwise. + */ + [[nodiscard]] bool IsProviderCompatibleWithType(const common::ProviderId provider_id, + common::CryptoProviderType requested_type) const; + + private: + /** + * @brief Create a default provider initialization configuration + * + * The default configuration: + * - Enables all available providers + * - Sets first available provider as default for basic types + * + * @return ProviderInitConfig with default settings + */ + config::ProviderInitConfig CreateDefaultConfig(); + + /** + * @brief Invoke all registered factories to create and register providers. + * + * Called once by Initialize(). Each registered IProviderFactory's + * CreateAndRegister() is called in registration order. + * + * @return true if all factory invocations succeeded, false otherwise + */ + bool CreateProviders(); + + /** + * @brief Initialize all registered providers with ProviderInitContext. + * + * Passes each provider a ProviderInitContext containing its assigned + * numeric ID and name. + * + * @return true if all providers initialized successfully, false otherwise + */ + bool InitializeAll(); + + /// Ordered list of factories to invoke during Initialize(). + std::vector> m_factories; + + /// Registry of providers by name: ProviderName -> ProviderEntry + std::unordered_map m_providers; + + /// Vector for lookup by numeric ID: m_provider_by_id[numeric_id] = instance + std::vector> m_provider_by_id; + + /// Mapping of provider type to numeric provider ID for type-based lookups + std::unordered_map m_typeToProviderId; + + /// Configuration reference + const score::crypto::daemon::config::Config& m_config; +}; + +} // namespace provider +} // namespace daemon +} // namespace score::crypto + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_MANAGER_HPP diff --git a/score/crypto/daemon/provider/score_provider/BUILD b/score/crypto/daemon/provider/score_provider/BUILD new file mode 100644 index 0000000..93bd5bf --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/BUILD @@ -0,0 +1,49 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +# Abstract base provider for the score interface family. +cc_library( + name = "score_provider", + srcs = ["src/score_provider.cpp"], + hdrs = ["score_provider.hpp"], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/daemon/provider:provider_headers", + "//score/crypto/daemon/provider/handler:crypto_handler_factory_headers", + ], +) + +# Score provider configuration (header-only entry struct + config class). +cc_library( + name = "score_provider_config", + hdrs = ["score_provider_config.hpp"], + visibility = ["//:__subpackages__"], +) + +# Top-level factory for the score interface family. +cc_library( + name = "score_provider_factory", + srcs = [ + "src/score_provider_config.cpp", + "src/score_provider_factory.cpp", + ], + hdrs = ["score_provider_factory.hpp"], + visibility = ["//:__subpackages__"], + deps = [ + ":score_provider", + ":score_provider_config", + "//score/crypto/daemon/provider:provider_headers", + "//score/crypto/daemon/provider/score_provider/openssl:provider_openssl_factory", + ], +) diff --git a/score/crypto/daemon/provider/score_provider/openssl/BUILD b/score/crypto/daemon/provider/score_provider/openssl/BUILD new file mode 100644 index 0000000..31a71a0 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/BUILD @@ -0,0 +1,100 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +# Header-only library for the OpenSSL key management primitives. +cc_library( + name = "openssl_key_management_headers", + hdrs = [ + "key_management/openssl_key_factory.hpp", + "key_management/openssl_key_handler.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/daemon/key_management:key_management_headers", + ], +) + +# Header-only library for OpenSSL algorithm detail headers. +cc_library( + name = "openssl_detail_headers", + hdrs = ["detail/openssl_algorithm_info.hpp"], + implementation_deps = ["//third_party/openssl"], + visibility = ["//:__subpackages__"], +) + +cc_library( + name = "provider_openssl_headers", + hdrs = [ + "openssl_provider_factory.hpp", + "operations/factory/openssl_handler_factory.hpp", + "operations/hash/openssl_hash_handler.hpp", + "operations/key_management/openssl_key_management_handler.hpp", + "operations/mac/openssl_hmac_handler.hpp", + "provider_openssl.hpp", + ], + includes = ["."], + visibility = ["//:__subpackages__"], + deps = [ + ":openssl_key_management_headers", + "//score/crypto/daemon/key_management:key_management_headers", + "//score/crypto/daemon/provider/executors:key_mgmt_executor", + "//score/crypto/daemon/provider/score_provider", + "//score/crypto/daemon/provider/score_provider/operations/factory:score_handler_factory", + "//score/crypto/daemon/provider/score_provider/operations/hash:score_hash_handler", + "//score/crypto/daemon/provider/score_provider/operations/key_management:score_key_management_handler", + "//score/crypto/daemon/provider/score_provider/operations/mac:score_mac_handler", + ], +) + +cc_library( + name = "provider_openssl_library", + srcs = [ + "detail/openssl_algorithm_info.hpp", + "key_management/openssl_key_factory.cpp", + "key_management/openssl_key_handler.cpp", + "operations/factory/openssl_handler_factory.cpp", + "operations/hash/openssl_hash_handler.cpp", + "operations/key_management/openssl_key_management_handler.cpp", + "operations/mac/openssl_hmac_handler.cpp", + "provider_openssl.cpp", + ], + implementation_deps = [ + "//third_party/openssl", + ], + includes = ["."], + linkstatic = True, + visibility = ["//:__subpackages__"], + deps = [ + ":openssl_detail_headers", + ":openssl_key_management_headers", + ":provider_openssl_headers", + "//score/crypto/daemon/common", + "//score/crypto/daemon/common:algorithm_info", + "//score/crypto/daemon/data_manager", + "//score/crypto/daemon/key_management", + "//score/crypto/daemon/provider:provider_headers", + "//score/crypto/daemon/provider/handler:handler_utils_impl", + ], +) + +cc_library( + name = "provider_openssl_factory", + srcs = ["openssl_provider_factory.cpp"], + hdrs = ["openssl_provider_factory.hpp"], + visibility = ["//:__subpackages__"], + deps = [ + ":provider_openssl_library", + "//score/crypto/daemon/provider:provider_headers", + ], +) diff --git a/score/crypto/daemon/provider/score_provider/openssl/detail/openssl_algorithm_info.hpp b/score/crypto/daemon/provider/score_provider/openssl/detail/openssl_algorithm_info.hpp new file mode 100644 index 0000000..e6968e5 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/detail/openssl_algorithm_info.hpp @@ -0,0 +1,87 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_DAEMON_PROVIDER_OPENSSL_DETAIL_OPENSSL_ALGORITHM_INFO_HPP +#define CRYPTO_DAEMON_PROVIDER_OPENSSL_DETAIL_OPENSSL_ALGORITHM_INFO_HPP + +#include + +#include +#include + +namespace score::crypto::daemon::provider::openssl::detail +{ + +/// @brief Algorithm → OpenSSL EVP_MD mapping entry. +struct OpensslDigestInfo +{ + std::string_view name; + const EVP_MD* (*evp_md_fn)(); ///< Function pointer returning the EVP_MD (avoids static init order) +}; + +/// @brief Static table of supported hash algorithms and their OpenSSL EVP_MD providers. +inline const OpensslDigestInfo kDigestAlgorithms[] = { + {"SHA256", EVP_sha256}, + {"SHA384", EVP_sha384}, + {"SHA512", EVP_sha512}, + {"SHA224", EVP_sha224}, + {"SHA1", EVP_sha1}, + {"MD5", EVP_md5}, +}; + +/// @brief Look up the EVP_MD for a hash algorithm name. +/// @return EVP_MD pointer, or nullptr if the algorithm is not supported. +[[nodiscard]] inline const EVP_MD* LookupHashEVPMD(std::string_view algorithm) noexcept +{ + for (const auto& entry : kDigestAlgorithms) + { + if (entry.name == algorithm) + { + return entry.evp_md_fn(); + } + } + return nullptr; +} + +/// @brief Algorithm → OpenSSL EVP_MD mapping for HMAC algorithms. +/// +/// HMAC algorithms use the same EVP_MD as their underlying digest. +/// The algorithm name is the HMAC-prefixed form (e.g. "HMAC-SHA256"). +struct OpensslHmacInfo +{ + std::string_view name; + const EVP_MD* (*evp_md_fn)(); +}; + +inline const OpensslHmacInfo kHmacAlgorithms[] = { + {"HMAC-SHA256", EVP_sha256}, + {"HMAC-SHA384", EVP_sha384}, + {"HMAC-SHA512", EVP_sha512}, +}; + +/// @brief Look up the EVP_MD for an HMAC algorithm name. +/// @return EVP_MD pointer, or nullptr if the algorithm is not supported. +[[nodiscard]] inline const EVP_MD* LookupHmacEVPMD(std::string_view algorithm) noexcept +{ + for (const auto& entry : kHmacAlgorithms) + { + if (entry.name == algorithm) + { + return entry.evp_md_fn(); + } + } + return nullptr; +} + +} // namespace score::crypto::daemon::provider::openssl::detail + +#endif // CRYPTO_DAEMON_PROVIDER_OPENSSL_DETAIL_OPENSSL_ALGORITHM_INFO_HPP diff --git a/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_factory.cpp b/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_factory.cpp new file mode 100644 index 0000000..d03d904 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_factory.cpp @@ -0,0 +1,88 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_factory.hpp" + +#include "score/crypto/daemon/common/algorithm_info.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.hpp" + +#include // OPENSSL_cleanse +#include // RAND_bytes + +#include +#include +#include + +namespace score::crypto::daemon::provider::openssl +{ + +OpenSslKeyFactory::OpenSslKeyFactory(common::ProviderId provider_id) : m_provider_id(provider_id) {}; + +::score::crypto::Expected +OpenSslKeyFactory::GenerateKey(const key_management::KeyGenerationRequest& request) +{ + const std::size_t key_size = DetermineKeySize(request.algorithm); + if (key_size == 0U) + { + return ::score::crypto::make_unexpected(::score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + std::vector key_bytes(key_size); + + if (RAND_bytes(key_bytes.data(), static_cast(key_size)) != 1) + { + OPENSSL_cleanse(key_bytes.data(), key_size); + return ::score::crypto::make_unexpected(::score::crypto::daemon::common::DaemonErrorCode::kOperationFailed); + } + + key_management::ProviderKeyHandle handle{}; + handle.opaque_id = static_cast( + reinterpret_cast(key_bytes.data())); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + handle.provider_id = m_provider_id; + handle.permissions = request.permissions; + handle.is_asymmetric = false; + handle.algorithm = request.algorithm; + handle.key_size = key_size; + + return std::make_shared(std::move(key_bytes), handle); +} + +::score::crypto::Expected +OpenSslKeyFactory::ImportKey(const key_management::KeyImportRequest& request) +{ + if ((request.key_data == nullptr) || (request.key_data_size == 0U)) + { + return ::score::crypto::make_unexpected(::score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + std::vector key_bytes(request.key_data, request.key_data + request.key_data_size); + + key_management::ProviderKeyHandle handle{}; + handle.opaque_id = static_cast( + reinterpret_cast(key_bytes.data())); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + handle.provider_id = m_provider_id; + handle.permissions = request.permissions; + handle.is_asymmetric = false; + handle.algorithm = request.algorithm; + handle.key_size = request.key_data_size; + + return std::make_shared(std::move(key_bytes), handle); +} + +// static +std::size_t OpenSslKeyFactory::DetermineKeySize(const common::AlgorithmId& algorithm) noexcept +{ + return ::score::crypto::daemon::common::LookupKeySize(std::string_view{algorithm.data(), algorithm.size()}) + .value_or(0U); +} + +} // namespace score::crypto::daemon::provider::openssl diff --git a/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_factory.hpp b/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_factory.hpp new file mode 100644 index 0000000..d805488 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_factory.hpp @@ -0,0 +1,74 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_OPENSSL_KEY_MANAGEMENT_OPENSSL_KEY_FACTORY_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_OPENSSL_KEY_MANAGEMENT_OPENSSL_KEY_FACTORY_HPP + +#include "score/crypto/daemon/key_management/interfaces/i_key_factory.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_handler.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_types.hpp" + +#include +#include +#include + +namespace score::crypto::daemon::provider::openssl +{ + +/// OpenSSL implementation of IKeyFactory. +/// +/// Key material model: +/// GenerateKey — RAND_bytes → heap buffer → OpenSslKeyHandler (owns unique_ptr) +/// ImportKey — memcpy → heap buffer → OpenSslKeyHandler +/// +/// Each returned IKeyHandler (OpenSslKeyHandler) owns exclusive heap memory for +/// one key. Zeroization uses OPENSSL_cleanse, which is guaranteed not to be +/// optimized away by the compiler. +/// +/// Thread safety: GenerateKey and ImportKey are safe to call concurrently +/// because each call allocates independent memory. The returned OpenSslKeyHandler +/// instances are not shared and require no additional locking. +class OpenSslKeyFactory final : public key_management::IKeyFactory +{ + public: + OpenSslKeyFactory(common::ProviderId provider_id); + ~OpenSslKeyFactory() override = default; + + OpenSslKeyFactory(const OpenSslKeyFactory&) = delete; + OpenSslKeyFactory& operator=(const OpenSslKeyFactory&) = delete; + OpenSslKeyFactory(OpenSslKeyFactory&&) = delete; + OpenSslKeyFactory& operator=(OpenSslKeyFactory&&) = delete; + + /// Generate a symmetric key using OpenSSL RAND_bytes. + /// + /// Key size is derived from request.algorithm: + /// HMAC-SHA256 → 32 B | HMAC-SHA384 → 48 B | HMAC-SHA512 → 64 B + /// AES-128-* → 16 B | AES-192-* → 24 B | AES-256-* → 32 B + [[nodiscard]] ::score::crypto::Expected + GenerateKey(const key_management::KeyGenerationRequest& request) override; + + /// Import raw key material by copying into a new heap buffer. + [[nodiscard]] ::score::crypto::Expected + ImportKey(const key_management::KeyImportRequest& request) override; + + private: + common::ProviderId m_provider_id{common::kInvalidProviderId}; + /// Map well-known algorithm names to symmetric key sizes in bytes. + /// Returns 0 for unknown algorithms. + [[nodiscard]] static std::size_t DetermineKeySize(const common::AlgorithmId& algorithm) noexcept; +}; + +} // namespace score::crypto::daemon::provider::openssl + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_OPENSSL_KEY_MANAGEMENT_OPENSSL_KEY_FACTORY_HPP diff --git a/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.cpp b/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.cpp new file mode 100644 index 0000000..f126a4d --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.cpp @@ -0,0 +1,84 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.hpp" + +#include // OPENSSL_cleanse +#include +#include + +namespace score::crypto::daemon::provider::openssl +{ + +OpenSslKeyHandler::OpenSslKeyHandler(std::vector key_bytes, + const key_management::ProviderKeyHandle& handle) noexcept + : m_key_bytes{std::move(key_bytes)}, m_handle{handle}, m_released{false} +{ +} + +OpenSslKeyHandler::~OpenSslKeyHandler() +{ + std::cout << "[OPENSSL_KEY_HANDLER] Release Key\n"; + static_cast(Release()); +} + +const key_management::ProviderKeyHandle& OpenSslKeyHandler::GetHandle() const noexcept +{ + return m_handle; +} + +common::ProviderId OpenSslKeyHandler::GetProviderId() const noexcept +{ + return m_handle.provider_id; +} + +::score::crypto::Expected OpenSslKeyHandler::Release() +{ + if (!m_released && !m_key_bytes.empty()) + { + OPENSSL_cleanse(m_key_bytes.data(), m_key_bytes.size()); + m_key_bytes.clear(); + m_released = true; + } + return std::monostate{}; +} + +const std::uint8_t* OpenSslKeyHandler::GetRawKeyBytes(std::size_t& out_size) const noexcept +{ + if (m_released || m_key_bytes.empty()) + { + out_size = 0U; + return nullptr; + } + out_size = m_key_bytes.size(); + return m_key_bytes.data(); +} + +::score::crypto::Expected +OpenSslKeyHandler::Export() const +{ + if (!score::mw::crypto::HasPermission(m_handle.permissions, score::mw::crypto::KeyOperationPermission::kExport)) + { + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kKeyOperationNotPermitted); + } + if (m_released || m_key_bytes.empty()) + { + return ::score::crypto::make_unexpected(::score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + key_management::SecureKeyBytes out(m_key_bytes.size()); + std::copy(m_key_bytes.begin(), m_key_bytes.end(), out.bytes.begin()); + return out; +} + +} // namespace score::crypto::daemon::provider::openssl diff --git a/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.hpp b/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.hpp new file mode 100644 index 0000000..1c6f0b5 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.hpp @@ -0,0 +1,66 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_OPENSSL_KEY_MANAGEMENT_OPENSSL_KEY_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_OPENSSL_KEY_MANAGEMENT_OPENSSL_KEY_HANDLER_HPP + +#include "score/crypto/daemon/key_management/interfaces/i_key_handler.hpp" + +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::openssl +{ + +/// Owns a single heap-allocated key material buffer. +/// +/// Crypto operation handlers (MAC, cipher) downcast the IKeyHandler to this +/// type and call GetRawKeyBytes() for direct access to the managed memory. +/// +/// Destruction calls Release() as a safety net; Release() is idempotent. +class OpenSslKeyHandler final : public key_management::IKeyHandler +{ + public: + OpenSslKeyHandler(std::vector key_bytes, const key_management::ProviderKeyHandle& handle) noexcept; + + ~OpenSslKeyHandler() override; + + OpenSslKeyHandler(const OpenSslKeyHandler&) = delete; + OpenSslKeyHandler& operator=(const OpenSslKeyHandler&) = delete; + OpenSslKeyHandler(OpenSslKeyHandler&&) = delete; + OpenSslKeyHandler& operator=(OpenSslKeyHandler&&) = delete; + + [[nodiscard]] const key_management::ProviderKeyHandle& GetHandle() const noexcept override; + + [[nodiscard]] ::score::crypto::Expected Release() + override; + + [[nodiscard]] ::score::crypto::Expected + Export() const override; + + [[nodiscard]] common::ProviderId GetProviderId() const noexcept override; + + /// Direct access to managed key material without opaque_id round-trip. + [[nodiscard]] const std::uint8_t* GetRawKeyBytes(std::size_t& out_size) const noexcept; + + private: + std::vector m_key_bytes; + key_management::ProviderKeyHandle m_handle; + bool m_released; +}; + +} // namespace score::crypto::daemon::provider::openssl + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_OPENSSL_KEY_MANAGEMENT_OPENSSL_KEY_HANDLER_HPP diff --git a/score/crypto/daemon/provider/score_provider/openssl/openssl_provider_factory.cpp b/score/crypto/daemon/provider/score_provider/openssl/openssl_provider_factory.cpp new file mode 100644 index 0000000..75844c4 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/openssl_provider_factory.cpp @@ -0,0 +1,31 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/openssl/openssl_provider_factory.hpp" + +#include + +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/provider_openssl.hpp" + +namespace score::crypto::daemon::provider::score_provider::openssl +{ + +bool OpenSSLProviderFactory::CreateAndRegister(ProviderManager& manager) +{ + auto openSSLProvider = std::make_shared(); + return manager.RegisterProvider( + common::kProviderNameOpenSSL, openSSLProvider, common::CryptoProviderType::SOFTWARE); +} + +} // namespace score::crypto::daemon::provider::score_provider::openssl diff --git a/score/crypto/daemon/provider/score_provider/openssl/openssl_provider_factory.hpp b/score/crypto/daemon/provider/score_provider/openssl/openssl_provider_factory.hpp new file mode 100644 index 0000000..2e2ffb9 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/openssl_provider_factory.hpp @@ -0,0 +1,47 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_PROVIDER_FACTORY_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_PROVIDER_FACTORY_HPP + +#include "score/crypto/daemon/provider/i_provider_factory.hpp" + +namespace score::crypto::daemon::provider::score_provider::openssl +{ + +/** + * @brief Factory that creates and registers the OpenSSL software provider. + * + * Constructs an openssl::OpenSSL instance and registers it under the + * common::kProviderNameOpenSSL name with CryptoProviderType::SOFTWARE. + * + * No configuration fields are required — the OpenSSL provider needs no + * per-token setup beyond what is compiled in. + */ +class OpenSSLProviderFactory final : public IProviderFactory +{ + public: + OpenSSLProviderFactory() = default; + ~OpenSSLProviderFactory() override = default; + + /** + * @brief Constructs an OpenSSL provider and registers it as SOFTWARE. + * + * @param manager The ProviderManager to register the provider into. + * @return true if the provider was registered successfully. + */ + bool CreateAndRegister(ProviderManager& manager) override; +}; + +} // namespace score::crypto::daemon::provider::score_provider::openssl + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_PROVIDER_FACTORY_HPP diff --git a/score/crypto/daemon/provider/score_provider/openssl/operations/factory/openssl_handler_factory.cpp b/score/crypto/daemon/provider/score_provider/openssl/operations/factory/openssl_handler_factory.cpp new file mode 100644 index 0000000..5589c8d --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/factory/openssl_handler_factory.cpp @@ -0,0 +1,71 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/openssl/operations/factory/openssl_handler_factory.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/provider/executors/key_mgmt_executor.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/operations/hash/openssl_hash_handler.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/operations/key_management/openssl_key_management_handler.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/hash/hash_executor.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/mac/mac_executor.hpp" +#include "score/result/result.h" +#include + +namespace score::crypto::daemon::provider::score_provider::openssl::handler +{ + +using HandlerSptr = ::score::crypto::daemon::provider::handler::Handler::Sptr; + +OpenSslHandlerFactory::OpenSslHandlerFactory(std::shared_ptr km_handler, + std::shared_ptr slot_handler, + key_management::KeyManagementService::Sptr km_service) + : ScoreHandlerFactory{std::move(km_handler), std::move(slot_handler), std::move(km_service)} +{ +} + +score::Result OpenSslHandlerFactory::CreateHashHandler(const common::AlgorithmId& algorithm) +{ + if (!OpenSslHashHandler::IsAlgorithmSupported(algorithm)) + { + score::result::Error error( + static_cast(score::mw::crypto::CryptoErrorCode::kUnsupportedAlgorithm), + score::mw::crypto::kCryptoErrorDomain, + "Algorithm not supported for handler: " + algorithm); + return score::Result(score::unexpect, error); + } + auto hash_executor = std::make_unique(); + return std::make_shared(std::move(hash_executor), algorithm); +} + +score::Result OpenSslHandlerFactory::CreateMacHandler(const common::AlgorithmId& algorithm) +{ + if (!OpenSslHmacHandler::IsAlgorithmSupported(algorithm)) + { + score::result::Error error( + static_cast(score::mw::crypto::CryptoErrorCode::kUnsupportedAlgorithm), + score::mw::crypto::kCryptoErrorDomain, + "Algorithm not supported for handler: " + algorithm); + return score::Result(score::unexpect, error); + } + auto mac_executor = std::make_unique(); + return std::make_shared(std::move(mac_executor), algorithm); +} + +score::Result OpenSslHandlerFactory::CreateKeyManagementHandler() +{ + auto executor = + std::make_unique(m_key_factory, m_slot_handler, m_km_service); + return std::make_shared(std::move(executor)); +} + +} // namespace score::crypto::daemon::provider::score_provider::openssl::handler diff --git a/score/crypto/daemon/provider/score_provider/openssl/operations/factory/openssl_handler_factory.hpp b/score/crypto/daemon/provider/score_provider/openssl/operations/factory/openssl_handler_factory.hpp new file mode 100644 index 0000000..9bd44a7 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/factory/openssl_handler_factory.hpp @@ -0,0 +1,43 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_OPERATIONS_FACTORY_OPENSSL_HANDLER_FACTORY_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_OPERATIONS_FACTORY_OPENSSL_HANDLER_FACTORY_HPP + +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/key_management/core/key_management_service.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_factory.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/factory/score_handler_factory.hpp" +#include "score/result/result.h" + +#include + +namespace score::crypto::daemon::provider::score_provider::openssl::handler +{ + +class OpenSslHandlerFactory final + : public ::score::crypto::daemon::provider::score_provider::operations::factory::ScoreHandlerFactory +{ + public: + OpenSslHandlerFactory(std::shared_ptr factory, + std::shared_ptr slot_handler, + key_management::KeyManagementService::Sptr km_service); + + ~OpenSslHandlerFactory() override = default; + + protected: + [[nodiscard]] ::score::Result<::score::crypto::daemon::provider::handler::Handler::Sptr> CreateHashHandler( + const common::AlgorithmId& algorithm) override; + [[nodiscard]] ::score::Result<::score::crypto::daemon::provider::handler::Handler::Sptr> CreateMacHandler( + const common::AlgorithmId& algorithm) override; + [[nodiscard]] ::score::Result<::score::crypto::daemon::provider::handler::Handler::Sptr> + CreateKeyManagementHandler() override; +}; + +} // namespace score::crypto::daemon::provider::score_provider::openssl::handler + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_OPERATIONS_FACTORY_OPENSSL_HANDLER_FACTORY_HPP diff --git a/score/crypto/daemon/provider/score_provider/openssl/operations/hash/openssl_hash_handler.cpp b/score/crypto/daemon/provider/score_provider/openssl/operations/hash/openssl_hash_handler.cpp new file mode 100644 index 0000000..54f815e --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/hash/openssl_hash_handler.cpp @@ -0,0 +1,375 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include +#include + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/handler/src/handler_utils.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/detail/openssl_algorithm_info.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/operations/hash/openssl_hash_handler.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::score_provider::openssl::handler +{ + +// Using declarations for convenience +using common::DaemonErrorCode; +using common::RequestParameters; +using common::ResponseParameters; +using common::StreamOperationState; + +// Static array initialization +static constexpr const char* SUPPORTED_ALGORITHMS[] = {"SHA256", "SHA384", "SHA512", "SHA224", "SHA1", "MD5"}; + +bool OpenSslHashHandler::IsAlgorithmSupported(const common::AlgorithmId& algorithm) noexcept +{ + for (const char* supported : SUPPORTED_ALGORITHMS) + { + if (algorithm == supported) + { + return true; + } + } + return false; +} + +OpenSslHashHandler::OpenSslHashHandler( + std::unique_ptr<::score::crypto::daemon::provider::score_provider::operations::hash::HashExecutor> executor, + common::AlgorithmId algorithm) + : ScoreHashHandler(std::move(executor), algorithm), mCurrentStreamContext(nullptr) +{ + // Operation support is defined by the executor +} + +OpenSslHashHandler::~OpenSslHashHandler() +{ + CleanupStreamContext(); +} + +Expected OpenSslHashHandler::ValidateAlgorithm(const std::string& algorithm) const +{ + for (const char* supported : SUPPORTED_ALGORITHMS) + { + if (algorithm == supported) + { + return std::monostate{}; + } + } + return make_unexpected(DaemonErrorCode::kUnsupportedAlgorithm); +} + +Expected OpenSslHashHandler::InitializeContext( + const ::score::crypto::daemon::provider::handler::InitializationParams& init_params) +{ + std::cout << "DEBUG: InitializeContext called with algorithm: " << m_algorithm << "\n"; + + // Validate the algorithm + const auto result = ValidateAlgorithm(m_algorithm); + if (!result.has_value()) + { + std::cout << "ERROR: Algorithm validation failed in InitializeContext\n"; + return result; + } + + // Call base to set state to IDLE + return ScoreHashHandler::InitializeContext(init_params); +} + +void OpenSslHashHandler::CleanupStreamContext() +{ + if (mCurrentStreamContext != nullptr) + { + EVP_MD_CTX_free(mCurrentStreamContext); + mCurrentStreamContext = nullptr; + } +} + +const EVP_MD* OpenSslHashHandler::GetEVPMD(const std::string& algorithm) const +{ + return ::score::crypto::daemon::provider::openssl::detail::LookupHashEVPMD(algorithm); +} + +Expected OpenSslHashHandler::Reset() +{ + CleanupStreamContext(); + m_state = StreamOperationState::IDLE; + return {}; +} + +Expected OpenSslHashHandler::StartHash( + const std::optional initialDataOrIV) +{ + std::cout << "DEBUG: StartHash called with algorithm: " << m_algorithm + << ", thread ID: " << std::this_thread::get_id() << ", this: " << this << "\n"; + const EVP_MD* md = GetEVPMD(m_algorithm); + if (md == nullptr) + { + return make_unexpected(DaemonErrorCode::kUnsupportedAlgorithm); + } + + // Initialize stream context if needed (OpenSSL-specific) + if (mCurrentStreamContext == nullptr) + { + mCurrentStreamContext = EVP_MD_CTX_new(); + if (mCurrentStreamContext == nullptr) + { + return make_unexpected(DaemonErrorCode::kContextCreationFailed); + } + } + + // Reset the context (OpenSSL-specific) + if (EVP_DigestInit_ex(mCurrentStreamContext, md, nullptr) != 1) + { + return make_unexpected(DaemonErrorCode::kAlgorithmInitializationFailed); + } + + // If initial data is provided, process it (OpenSSL-specific) + if (initialDataOrIV.has_value()) + { + const uint8_t* buffer = nullptr; + size_t size = 0; + + const auto result = ::score::crypto::daemon::provider::handler::handler_utils::ExtractBufferData( + initialDataOrIV.value(), buffer, size); + if (!result.has_value()) + { + return result; + } + + if (EVP_DigestUpdate(mCurrentStreamContext, buffer, size) != 1) + { + return make_unexpected(DaemonErrorCode::kAlgorithmExecutionFailed); + } + } + + return std::monostate{}; +} + +Expected OpenSslHashHandler::UpdateHash(const common::RequestParameter& dataToHash) +{ + // Validate stream context exists (OpenSSL-specific) + if (mCurrentStreamContext == nullptr) + { + return make_unexpected(DaemonErrorCode::kStreamNotInitialized); + } + + // Extract and update with data (OpenSSL-specific) + const uint8_t* buffer = nullptr; + size_t size = 0; + + const auto result = + ::score::crypto::daemon::provider::handler::handler_utils::ExtractBufferData(dataToHash, buffer, size); + if (!result.has_value()) + { + return result; + } + + if (EVP_DigestUpdate(mCurrentStreamContext, buffer, size) != 1) + { + return make_unexpected(DaemonErrorCode::kAlgorithmExecutionFailed); + } + + return std::monostate{}; +} + +Expected OpenSslHashHandler::FinalizeHash( + std::optional hashOutput, + const std::optional finalDataToHash) +{ + if (mCurrentStreamContext == nullptr) + { + return make_unexpected(DaemonErrorCode::kStreamNotInitialized); + } + + // Process final data if provided (OpenSSL-specific) + if (finalDataToHash.has_value()) + { + const uint8_t* buffer = nullptr; + size_t size = 0; + + const auto result = ::score::crypto::daemon::provider::handler::handler_utils::ExtractBufferData( + finalDataToHash.value(), buffer, size); + if (!result.has_value()) + { + return make_unexpected(result.error()); + } + + if (EVP_DigestUpdate(mCurrentStreamContext, buffer, size) != 1) + { + CleanupStreamContext(); + return make_unexpected(DaemonErrorCode::kAlgorithmExecutionFailed); + } + } + + // Get the hash size (OpenSSL-specific) + unsigned int digestSize = EVP_MD_CTX_size(mCurrentStreamContext); + if (digestSize == 0) + { + CleanupStreamContext(); + return make_unexpected(DaemonErrorCode::kAlgorithmExecutionFailed); + } + + // Resolve output buffer: validate size if caller-provided, else allocate internally + unsigned char* outputBuf = nullptr; + auto allocateOutputBuffer = !hashOutput.has_value(); + if (!allocateOutputBuffer) + { + common::RequestParameter& outputRef = hashOutput.value(); + uint8_t* outputBuffer = nullptr; + size_t outputSize = 0; + const auto result = ::score::crypto::daemon::provider::handler::handler_utils::ExtractOutputBufferData( + outputRef, outputBuffer, outputSize); + if (!result.has_value()) + { + CleanupStreamContext(); + return make_unexpected(result.error()); + } + if (outputSize < digestSize) + { + CleanupStreamContext(); + return make_unexpected(DaemonErrorCode::kInsufficientBufferSize); + } + outputBuf = outputBuffer; + } + else + { + AllocateOutputBuffer(digestSize); + outputBuf = mOutputBuffer.data(); + } + + unsigned int digestLen = 0; + if (EVP_DigestFinal_ex(mCurrentStreamContext, outputBuf, &digestLen) != 1) + { + CleanupStreamContext(); + return make_unexpected(DaemonErrorCode::kAlgorithmExecutionFailed); + } + + // Clean up the context + CleanupStreamContext(); + + common::ResponseParameters response; + if (allocateOutputBuffer) + { + response.push_back(common::OwnedBuffer{std::move(mOutputBuffer)}); + } + else + { + response.push_back(common::VirtualMemoryBufferConst{outputBuf, digestLen}); + } + + return response; +} + +Expected OpenSslHashHandler::SingleShotHash( + const common::RequestParameter& dataToHash, + std::optional outputHash, + std::optional initializationVector) +{ + if (m_algorithm.empty()) + { + return make_unexpected(DaemonErrorCode::kInsufficientParameters); + } + + const auto algResult = ValidateAlgorithm(m_algorithm); + if (!algResult.has_value()) + { + return make_unexpected(algResult.error()); + } + + const EVP_MD* md = GetEVPMD(m_algorithm); + if (md == nullptr) + { + return make_unexpected(DaemonErrorCode::kUnsupportedAlgorithm); + } + + unsigned int digestSize = EVP_MD_size(md); + + // Extract input data + const uint8_t* inputBuffer = nullptr; + size_t inputSize = 0; + + const auto inputResult = ::score::crypto::daemon::provider::handler::handler_utils::ExtractBufferData( + dataToHash, inputBuffer, inputSize); + if (!inputResult.has_value()) + { + return make_unexpected(inputResult.error()); + } + + // Determine output destination + unsigned char* outputBuf = nullptr; + unsigned int digestLen = 0; + + auto allocateOutputBuffer = !outputHash.has_value(); + if (!allocateOutputBuffer) + { + common::RequestParameter& outputRef = outputHash.value(); + uint8_t* outputBuffer = nullptr; + size_t outputSize = 0; + + const auto outputResult = ::score::crypto::daemon::provider::handler::handler_utils::ExtractOutputBufferData( + outputRef, outputBuffer, outputSize); + if (!outputResult.has_value()) + { + return make_unexpected(outputResult.error()); + } + + if (outputSize < digestSize) + { + return make_unexpected(DaemonErrorCode::kInsufficientBufferSize); + } + + outputBuf = outputBuffer; + } + else + { + AllocateOutputBuffer(digestSize); + outputBuf = mOutputBuffer.data(); + } + + // Single OpenSSL call: handles context creation, init, update, final, and cleanup internally + if (EVP_Digest(inputBuffer, inputSize, outputBuf, &digestLen, md, nullptr) != 1) + { + return make_unexpected(DaemonErrorCode::kAlgorithmExecutionFailed); + } + + common::ResponseParameters response; + if (allocateOutputBuffer) + { + response.push_back(common::OwnedBuffer{std::move(mOutputBuffer)}); + } + else + { + response.push_back(common::VirtualMemoryBufferConst{outputBuf, digestLen}); + } + + return response; +} + +void OpenSslHashHandler::AllocateOutputBuffer(size_t size) +{ + mOutputBuffer.clear(); + mOutputBuffer.resize(size); + std::cout << "[HASH_HANDLER] Output buffer allocated with size: " << size << std::endl; +} + +} // namespace score::crypto::daemon::provider::score_provider::openssl::handler diff --git a/score/crypto/daemon/provider/score_provider/openssl/operations/hash/openssl_hash_handler.hpp b/score/crypto/daemon/provider/score_provider/openssl/operations/hash/openssl_hash_handler.hpp new file mode 100644 index 0000000..6bc4888 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/hash/openssl_hash_handler.hpp @@ -0,0 +1,76 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_OPERATIONS_HASH_OPENSSL_HASH_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_OPERATIONS_HASH_OPENSSL_HASH_HANDLER_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/hash/hash_executor.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/hash/score_hash_handler.hpp" +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::score_provider::openssl::handler +{ + +class OpenSslHashHandler final + : public ::score::crypto::daemon::provider::score_provider::operations::hash::ScoreHashHandler +{ + public: + using Sptr = std::shared_ptr; + + explicit OpenSslHashHandler( + std::unique_ptr<::score::crypto::daemon::provider::score_provider::operations::hash::HashExecutor> executor, + common::AlgorithmId algorithm); + ~OpenSslHashHandler() override; + + // Handler interface overrides (OpenSSL-specific initialization and cleanup) + Expected InitializeContext( + const ::score::crypto::daemon::provider::handler::InitializationParams& init_params) override; + Expected Reset() override; + + // ScoreHashHandler typed method overrides (OpenSSL crypto implementation) + Expected StartHash( + const std::optional initialDataOrIV) override; + Expected UpdateHash(const common::RequestParameter& dataToHash) override; + Expected FinalizeHash( + std::optional hashOutput, + const std::optional finalDataToHash) override; + Expected SingleShotHash( + const common::RequestParameter& dataToHash, + std::optional outputHash, + std::optional initializationVector) override; + + /// @brief Check if the given algorithm is supported by this handler. + [[nodiscard]] static bool IsAlgorithmSupported(const common::AlgorithmId& algorithm) noexcept; + + private: + // OpenSSL-specific stream context management + EVP_MD_CTX* mCurrentStreamContext; + + // Output buffer - handler owns allocation and lifecycle + std::vector mOutputBuffer; + + // Helper methods (OpenSSL provider-specific) + const EVP_MD* GetEVPMD(const std::string& algorithm) const; + Expected ValidateAlgorithm(const std::string& algorithm) const; + void CleanupStreamContext(); + void AllocateOutputBuffer(size_t size); +}; + +} // namespace score::crypto::daemon::provider::score_provider::openssl::handler + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_OPERATIONS_HASH_OPENSSL_HASH_HANDLER_HPP diff --git a/score/crypto/daemon/provider/score_provider/openssl/operations/key_management/openssl_key_management_handler.cpp b/score/crypto/daemon/provider/score_provider/openssl/operations/key_management/openssl_key_management_handler.cpp new file mode 100644 index 0000000..c72e0ed --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/key_management/openssl_key_management_handler.cpp @@ -0,0 +1,24 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/openssl/operations/key_management/openssl_key_management_handler.hpp" + +namespace score::crypto::daemon::provider::score_provider::openssl::handler +{ + +OpenSslKeyManagementHandler::OpenSslKeyManagementHandler( + std::unique_ptr executor) + : ScoreKeyManagementHandler{std::move(executor)} +{ +} + +} // namespace score::crypto::daemon::provider::score_provider::openssl::handler diff --git a/score/crypto/daemon/provider/score_provider/openssl/operations/key_management/openssl_key_management_handler.hpp b/score/crypto/daemon/provider/score_provider/openssl/operations/key_management/openssl_key_management_handler.hpp new file mode 100644 index 0000000..b7f3362 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/key_management/openssl_key_management_handler.hpp @@ -0,0 +1,41 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_OPERATIONS_KEY_MANAGEMENT_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_OPERATIONS_KEY_MANAGEMENT_HANDLER_HPP + +#include "score/crypto/daemon/provider/executors/key_mgmt_executor.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/key_management/score_key_management_handler.hpp" + +#include + +namespace score::crypto::daemon::provider::score_provider::openssl::handler +{ + +/// OpenSSL-specific key management handler. +/// Currently delegates entirely to ScoreKeyManagementHandler base. +class OpenSslKeyManagementHandler final + : public ::score::crypto::daemon::provider::score_provider::operations::key_management::ScoreKeyManagementHandler +{ + public: + explicit OpenSslKeyManagementHandler(std::unique_ptr executor); + ~OpenSslKeyManagementHandler() override = default; + + OpenSslKeyManagementHandler(const OpenSslKeyManagementHandler&) = delete; + OpenSslKeyManagementHandler& operator=(const OpenSslKeyManagementHandler&) = delete; + OpenSslKeyManagementHandler(OpenSslKeyManagementHandler&&) = delete; + OpenSslKeyManagementHandler& operator=(OpenSslKeyManagementHandler&&) = delete; +}; + +} // namespace score::crypto::daemon::provider::score_provider::openssl::handler + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_OPERATIONS_KEY_MANAGEMENT_HANDLER_HPP diff --git a/score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.cpp b/score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.cpp new file mode 100644 index 0000000..262e0fa --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.cpp @@ -0,0 +1,418 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.hpp" +#include "score/crypto/daemon/common/algorithm_info.hpp" +#include "score/crypto/daemon/provider/handler/src/handler_utils.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/detail/openssl_algorithm_info.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.hpp" + +#include // CRYPTO_memcmp, OPENSSL_cleanse +#include // OSSL_PARAM + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include +#include + +namespace score::crypto::daemon::provider::score_provider::openssl::handler +{ + +using common::OperationIdentifier; +using common::RequestParameters; +using common::ResponseParameters; +using common::StreamOperationState; +using ::score::crypto::daemon::common::DaemonErrorCode; +namespace handler_utils = ::score::crypto::daemon::provider::handler::handler_utils; + +// --------------------------------------------------------------------------- +// Supported algorithms +// --------------------------------------------------------------------------- + +static constexpr const char* kSupportedAlgorithms[] = { + "HMAC-SHA256", + "HMAC-SHA384", + "HMAC-SHA512", +}; + +// --------------------------------------------------------------------------- +// Construction / destruction +// --------------------------------------------------------------------------- + +OpenSslHmacHandler::OpenSslHmacHandler(std::unique_ptr executor, + const common::AlgorithmId& algorithm) + : ScoreMacHandler{std::move(executor), algorithm} +{ +} + +OpenSslHmacHandler::~OpenSslHmacHandler() +{ + CleanupContext(); +} + +void OpenSslHmacHandler::CleanupContext() noexcept +{ + if (m_ctx != nullptr) + { + EVP_MAC_CTX_free(m_ctx); + m_ctx = nullptr; + } + if (m_mac != nullptr) + { + EVP_MAC_free(m_mac); + m_mac = nullptr; + } + OPENSSL_cleanse(m_output_buffer.data(), m_output_buffer.size()); + m_output_buffer.clear(); +} + +// --------------------------------------------------------------------------- +// Static helpers +// --------------------------------------------------------------------------- + +const char* OpenSslHmacHandler::GetDigestName() const noexcept +{ + const std::string_view algo{m_algorithm.data(), m_algorithm.size()}; + if (algo == "HMAC-SHA256") + { + return "SHA256"; + } + if (algo == "HMAC-SHA384") + { + return "SHA384"; + } + if (algo == "HMAC-SHA512") + { + return "SHA512"; + } + return nullptr; +} + +bool OpenSslHmacHandler::IsAlgorithmSupported(const common::AlgorithmId& algorithm) noexcept +{ + for (const char* supported : kSupportedAlgorithms) + { + if (algorithm == supported) + { + return true; + } + } + return false; +} + +std::size_t OpenSslHmacHandler::GetMacSize() const noexcept +{ + return ::score::crypto::daemon::common::LookupMacSize(std::string_view{m_algorithm.data(), m_algorithm.size()}) + .value_or(0U); +} + +// --------------------------------------------------------------------------- +// Handler interface +// --------------------------------------------------------------------------- + +::score::crypto::Expected +OpenSslHmacHandler::InitializeContext( + const ::score::crypto::daemon::provider::handler::InitializationParams& init_params) +{ + // Validate algorithm (m_algorithm is set at construction). + bool found{false}; + for (const char* algo : kSupportedAlgorithms) + { + if (m_algorithm == algo) + { + found = true; + break; + } + } + if (!found) + { + std::cerr << LOG_PREFIX << "Unsupported algorithm: " << m_algorithm << '\n'; + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kUnsupportedAlgorithm); + } + + m_state = StreamOperationState::IDLE; + + // Fetch the EVP_MAC object for HMAC and allocate context. + CleanupContext(); + m_mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr); + if (m_mac == nullptr) + { + std::cerr << LOG_PREFIX << "EVP_MAC_fetch(\"HMAC\") failed\n"; + return ::score::crypto::make_unexpected(::score::crypto::daemon::common::DaemonErrorCode::kAllocationFailed); + } + m_ctx = EVP_MAC_CTX_new(m_mac); + if (m_ctx == nullptr) + { + std::cerr << LOG_PREFIX << "EVP_MAC_CTX_new failed\n"; + EVP_MAC_free(m_mac); + m_mac = nullptr; + return ::score::crypto::make_unexpected(::score::crypto::daemon::common::DaemonErrorCode::kAllocationFailed); + } + + m_output_buffer.resize(GetMacSize()); + + // Bind key if provided via InitializationParams (bound_key_handler != nullptr). + if (init_params.bound_key_handler != nullptr) + { + // Provider-id check validates the key comes from the same provider (no dynamic_cast/RTTI). + if (init_params.bound_key_handler->GetProviderId() != 0) // OPENSSL provider ID + { + std::cerr << LOG_PREFIX << "InitializeContext: bound key is not an OpenSSL key handler\n"; + return ::score::crypto::make_unexpected(::score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) � type tag verified above + const auto* openssl_key = static_cast( + init_params.bound_key_handler); + + std::size_t key_len{0U}; + const uint8_t* key_bytes = openssl_key->GetRawKeyBytes(key_len); + if (key_bytes == nullptr || key_len == 0U) + { + std::cerr << LOG_PREFIX << "InitializeContext: invalid key handle\n"; + return ::score::crypto::make_unexpected(::score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + // Key is valid � store params so InitMac() and Reset() can use them. + m_init_params = init_params; + m_state = StreamOperationState::STREAM_INIT; + } + + return std::monostate{}; +} + +::score::crypto::Expected +OpenSslHmacHandler::StartMac(const std::optional /*initialDataOrIV*/) +{ + if (m_ctx == nullptr) + { + std::cerr << LOG_PREFIX << "StartMac: HMAC context not allocated\n"; + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kStreamNotInitialized); + } + + const uint8_t* key_bytes{nullptr}; + std::size_t key_len{0U}; + if (!GetBoundKeyMaterial(key_bytes, key_len)) + { + std::cerr << LOG_PREFIX << "StartMac: no valid key material \u2014 call InitializeContext with a key first\n"; + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kStreamNotInitialized); + } + + const char* digest_name = GetDigestName(); + if (digest_name == nullptr) + { + std::cerr << LOG_PREFIX << "StartMac: unsupported digest for algorithm " << m_algorithm << '\n'; + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kUnsupportedAlgorithm); + } + + // EVP_MAC_init with key + OSSL_PARAM for digest algorithm + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) ? OpenSSL OSSL_PARAM API requires non-const char* + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string("digest", const_cast(digest_name), 0), + OSSL_PARAM_construct_end(), + }; + + const int rv = EVP_MAC_init(m_ctx, key_bytes, key_len, params); + if (rv != 1) + { + std::cerr << LOG_PREFIX << "StartMac: EVP_MAC_init failed\n"; + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kAlgorithmInitializationFailed); + } + + return std::monostate{}; +} + +bool OpenSslHmacHandler::GetBoundKeyMaterial(const uint8_t*& key_bytes, std::size_t& key_len) const noexcept +{ + if (m_init_params.bound_key_handler == nullptr) + { + return false; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) � type tag verified in InitializeContext + const auto* openssl_key = static_cast( + m_init_params.bound_key_handler); + key_bytes = openssl_key->GetRawKeyBytes(key_len); + return key_bytes != nullptr && key_len > 0U; +} + +::score::crypto::Expected OpenSslHmacHandler::Reset() +{ + return InitializeContext(m_init_params); +} + +// --------------------------------------------------------------------------- +// MacHandler interface +// --------------------------------------------------------------------------- + +::score::crypto::Expected +OpenSslHmacHandler::UpdateMac(const common::RequestParameter& dataToMac) +{ + if (m_ctx == nullptr || m_state == StreamOperationState::IDLE) + { + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kStreamNotInitialized); + } + + const uint8_t* data{nullptr}; + std::size_t data_len{0U}; + auto extract = handler_utils::ExtractBufferData(dataToMac, data, data_len); + if (!extract.has_value()) + { + return ::score::crypto::make_unexpected(extract.error()); + } + + const int rv = EVP_MAC_update(m_ctx, data, data_len); + if (rv != 1) + { + std::cerr << LOG_PREFIX << "EVP_MAC_update failed\n"; + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + return std::monostate{}; +} + +::score::crypto::Expected +OpenSslHmacHandler::FinalizeMac(std::optional macOutput, + const std::optional finalDataToMac) +{ + // If final data is provided, feed it before producing the tag. + if (finalDataToMac.has_value()) + { + auto update_result = UpdateMac(finalDataToMac.value()); + if (!update_result.has_value()) + { + return ::score::crypto::make_unexpected(update_result.error()); + } + } + + const std::size_t mac_size = GetMacSize(); + const bool allocateOutputBuffer = !macOutput.has_value(); + + // Resolve output buffer: caller-provided or internal. + uint8_t* outputBuf = nullptr; + std::size_t outputBufSize = 0U; + if (!allocateOutputBuffer) + { + common::RequestParameter& outputRef = macOutput.value(); + const auto result = handler_utils::ExtractOutputBufferData(outputRef, outputBuf, outputBufSize); + if (!result.has_value()) + { + return ::score::crypto::make_unexpected(result.error()); + } + if (outputBufSize < mac_size) + { + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + } + else + { + AllocateOutputBuffer(mac_size); + outputBuf = m_output_buffer.data(); + outputBufSize = m_output_buffer.size(); + } + + std::size_t hmac_len{0U}; + const auto raw_res = FinalizeMacInternal(outputBuf, outputBufSize, hmac_len); + if (!raw_res.has_value()) + { + return ::score::crypto::make_unexpected(raw_res.error()); + } + + common::ResponseParameters response; + if (allocateOutputBuffer) + { + response.push_back(common::OwnedBuffer{std::move(m_output_buffer)}); + } + else + { + response.push_back(common::VirtualMemoryBufferConst{outputBuf, hmac_len}); + } + return response; +} + +::score::crypto::Expected +OpenSslHmacHandler::FinalizeMacInternal(uint8_t* output_buf, std::size_t buf_len, std::size_t& out_len) +{ + if (m_ctx == nullptr) + { + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kStreamNotInitialized); + } + + const std::size_t mac_size = GetMacSize(); + if (buf_len < mac_size) + { + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + + std::size_t hmac_len = 0U; + const int rv = EVP_MAC_final(m_ctx, output_buf, &hmac_len, buf_len); + if (rv != 1) + { + std::cerr << LOG_PREFIX << "EVP_MAC_final failed\n"; + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kAlgorithmExecutionFailed); + } + + out_len = hmac_len; + m_state = StreamOperationState::IDLE; + return std::monostate{}; +} + +::score::crypto::Expected OpenSslHmacHandler::VerifyMac( + const common::RequestParameter& expectedTag) +{ + const uint8_t* tag{nullptr}; + std::size_t tag_len{0U}; + auto extract = handler_utils::ExtractBufferData(expectedTag, tag, tag_len); + if (!extract.has_value()) + { + return ::score::crypto::make_unexpected(extract.error()); + } + + if (tag_len != GetMacSize()) + { + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kInsufficientBufferSize); + } + + AllocateOutputBuffer(GetMacSize()); + std::size_t out_len{0U}; + const auto final_res = FinalizeMacInternal(m_output_buffer.data(), m_output_buffer.size(), out_len); + if (!final_res.has_value()) + { + OPENSSL_cleanse(m_output_buffer.data(), m_output_buffer.size()); + return ::score::crypto::make_unexpected(final_res.error()); + } + + // Constant-time comparison. + const int match = CRYPTO_memcmp(m_output_buffer.data(), tag, out_len); + OPENSSL_cleanse(m_output_buffer.data(), m_output_buffer.size()); + return match == 0; +} + +// --------------------------------------------------------------------------- +// Helper +// --------------------------------------------------------------------------- + +void OpenSslHmacHandler::AllocateOutputBuffer(std::size_t size) +{ + m_output_buffer.clear(); + m_output_buffer.resize(size); +} + +} // namespace score::crypto::daemon::provider::score_provider::openssl::handler diff --git a/score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.hpp b/score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.hpp new file mode 100644 index 0000000..3650918 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.hpp @@ -0,0 +1,110 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_OPERATIONS_MAC_OPENSSL_HMAC_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_OPERATIONS_MAC_OPENSSL_HMAC_HANDLER_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/handler/handler_init_params.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/mac/mac_executor.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/mac/score_mac_handler.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::score_provider::openssl::handler +{ + +class OpenSslHmacHandler final + : public ::score::crypto::daemon::provider::score_provider::operations::mac::ScoreMacHandler +{ + public: + using Sptr = std::shared_ptr; + + explicit OpenSslHmacHandler(std::unique_ptr executor, + const common::AlgorithmId& algorithm); + ~OpenSslHmacHandler() override; + + OpenSslHmacHandler(const OpenSslHmacHandler&) = delete; + OpenSslHmacHandler& operator=(const OpenSslHmacHandler&) = delete; + OpenSslHmacHandler(OpenSslHmacHandler&&) = delete; + OpenSslHmacHandler& operator=(OpenSslHmacHandler&&) = delete; + + // ----------------------------------------------------------------------- + // Handler interface + // ----------------------------------------------------------------------- + + [[nodiscard]] ::score::crypto::Expected + InitializeContext(const ::score::crypto::daemon::provider::handler::InitializationParams& init_params) override; + + [[nodiscard]] ::score::crypto::Expected Reset() + override; + + // ----------------------------------------------------------------------- + // MacHandler interface + // ----------------------------------------------------------------------- + + [[nodiscard]] ::score::crypto::Expected StartMac( + const std::optional initialDataOrIV) override; + + [[nodiscard]] ::score::crypto::Expected UpdateMac( + const common::RequestParameter& dataToMac) override; + + [[nodiscard]] ::score::crypto::Expected + FinalizeMac(std::optional macOutput, + const std::optional finalDataToMac) override; + + [[nodiscard]] ::score::crypto::Expected VerifyMac( + const common::RequestParameter& expectedTag) override; + + [[nodiscard]] std::size_t GetMacSize() const noexcept override; + + /// @brief Check if the given algorithm is supported by this handler. + [[nodiscard]] static bool IsAlgorithmSupported(const common::AlgorithmId& algorithm) noexcept; + + private: + /// @brief Map HMAC algorithm string (e.g. "HMAC-SHA256") to the underlying digest name + /// expected by EVP_MAC (e.g. "SHA256"). + [[nodiscard]] const char* GetDigestName() const noexcept; + + /// @brief Retrieve the bound key's raw bytes. + /// @return true if key material is available, false if no key is bound. + [[nodiscard]] bool GetBoundKeyMaterial(const uint8_t*& key_bytes, std::size_t& key_len) const noexcept; + + /// @brief Destroy and zero the EVP_MAC context. + void CleanupContext() noexcept; + + /// @brief Allocate (or resize) the internal output buffer. + void AllocateOutputBuffer(std::size_t size); + + [[nodiscard]] ::score::crypto::Expected + FinalizeMacInternal(uint8_t* output_buf, std::size_t buf_len, std::size_t& out_len); + + EVP_MAC* m_mac{nullptr}; + EVP_MAC_CTX* m_ctx{nullptr}; + std::vector m_output_buffer; + ::score::crypto::daemon::provider::handler::InitializationParams m_init_params; + + static constexpr std::string_view LOG_PREFIX = "[OPENSSL_HMAC_HANDLER] "; +}; + +} // namespace score::crypto::daemon::provider::score_provider::openssl::handler + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_OPERATIONS_MAC_OPENSSL_HMAC_HANDLER_HPP diff --git a/score/crypto/daemon/provider/score_provider/openssl/provider_openssl.cpp b/score/crypto/daemon/provider/score_provider/openssl/provider_openssl.cpp new file mode 100644 index 0000000..0c56db2 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/provider_openssl.cpp @@ -0,0 +1,95 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/openssl/provider_openssl.hpp" +#include "score/crypto/daemon/key_management/slot/file_backed_slot_handler.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_factory.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/operations/factory/openssl_handler_factory.hpp" +#include +#include + +namespace score::crypto::daemon::provider::score_provider::openssl +{ + +OpenSSL::OpenSSL() : m_factory(nullptr) {} + +OpenSSL::~OpenSSL() +{ + Shutdown(); +} + +bool OpenSSL::Initialize(const ProviderInitContext& ctx) +{ + if (m_initialized) + { + return true; + } + + // Base class stores ID and name. + ScoreProvider::Initialize(ctx); + + if (!OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS | OPENSSL_INIT_ADD_ALL_CIPHERS | + OPENSSL_INIT_ADD_ALL_DIGESTS | OPENSSL_INIT_LOAD_CONFIG, + nullptr)) + { + std::cerr << "[OpenSSL] Error: Failed to initialize OpenSSL\n"; + m_initialized = false; + return false; + } + std::cout << "[OpenSSL] Initialized successfully (ID: " << m_numeric_id << ", Name: " << m_provider_name << ")\n"; + + // Create key factory. + m_factory = std::make_shared<::score::crypto::daemon::provider::openssl::OpenSslKeyFactory>(m_numeric_id); + + m_initialized = true; + return m_initialized; +} + +void OpenSSL::Shutdown() +{ + if (!m_initialized) + { + return; + } + + m_factory.reset(); + m_keyManagementService.reset(); + + // Clean up OpenSSL resources + OPENSSL_cleanup(); + + // Base class resets factory and flags. + ScoreProvider::Shutdown(); +} + +std::shared_ptr<::score::crypto::daemon::provider::handler::ICryptoHandlerFactory> OpenSSL::CreateHandlerFactory() +{ + return std::make_shared(m_factory, GetKeySlotHandler({}), m_keyManagementService); +} + +std::shared_ptr OpenSSL::GetKeyFactory() +{ + return m_factory; +} + +::score::crypto::daemon::key_management::IKeySlotHandler::Sptr OpenSSL::GetKeySlotHandler( + const ::score::crypto::daemon::key_management::KeySlotConfig& /*config*/) +{ + return std::make_shared(m_factory); +} + +void OpenSSL::SetKeyManagementService(std::shared_ptr service) +{ + m_keyManagementService = std::move(service); +} + +} // namespace score::crypto::daemon::provider::score_provider::openssl diff --git a/score/crypto/daemon/provider/score_provider/openssl/provider_openssl.hpp b/score/crypto/daemon/provider/score_provider/openssl/provider_openssl.hpp new file mode 100644 index 0000000..839e8ea --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/provider_openssl.hpp @@ -0,0 +1,62 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_PROVIDER_OPENSSL_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_PROVIDER_OPENSSL_HPP + +#include + +#include "score/crypto/daemon/provider/score_provider/score_provider.hpp" + +namespace score::crypto::daemon::provider::score_provider::openssl +{ + +/// @brief OpenSSL software-only crypto provider. +/// +/// Inherits ScoreProvider for lifecycle and lazy factory creation. +/// Adds OpenSSL-specific initialization (OPENSSL_init_crypto) and +/// key management (in-process key generation/import/release with optional +/// file-backed persistence via FileBackedSlotHandler). +class OpenSSL final : public ::score::crypto::daemon::provider::score_provider::ScoreProvider +{ + public: + OpenSSL(); + ~OpenSSL() override; + + OpenSSL(const OpenSSL&) = delete; + OpenSSL& operator=(const OpenSSL&) = delete; + OpenSSL(OpenSSL&&) = delete; + OpenSSL& operator=(OpenSSL&&) = delete; + + // --- IProvider lifecycle (OpenSSL-specific) --- + bool Initialize(const ProviderInitContext& ctx) override; + void Shutdown() override; + + // --- Key management capability --- + std::shared_ptr GetKeyFactory() override; + std::shared_ptr GetKeySlotHandler( + const key_management::KeySlotConfig& config) override; + void SetKeyManagementService(std::shared_ptr service) override; + + protected: + /// Creates the OpenSSL-specific handler factory. + [[nodiscard]] std::shared_ptr<::score::crypto::daemon::provider::handler::ICryptoHandlerFactory> + CreateHandlerFactory() override; + + private: + std::shared_ptr m_factory; + std::shared_ptr m_keyManagementService; +}; + +} // namespace score::crypto::daemon::provider::score_provider::openssl + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPENSSL_PROVIDER_OPENSSL_HPP diff --git a/score/crypto/daemon/provider/score_provider/operations/factory/BUILD b/score/crypto/daemon/provider/score_provider/operations/factory/BUILD new file mode 100644 index 0000000..ba51d44 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/factory/BUILD @@ -0,0 +1,27 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +# Abstract base handler factory for the score interface family. +cc_library( + name = "score_handler_factory", + srcs = ["src/score_handler_factory.cpp"], + hdrs = ["score_handler_factory.hpp"], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/daemon/common", + "//score/crypto/daemon/key_management", + "//score/crypto/daemon/provider/handler:crypto_handler_factory_headers", + "@score_baselibs//score/result", + ], +) diff --git a/score/crypto/daemon/provider/score_provider/operations/factory/score_handler_factory.hpp b/score/crypto/daemon/provider/score_provider/operations/factory/score_handler_factory.hpp new file mode 100644 index 0000000..367fca4 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/factory/score_handler_factory.hpp @@ -0,0 +1,74 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_FACTORY_SCORE_HANDLER_FACTORY_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_FACTORY_SCORE_HANDLER_FACTORY_HPP + +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/key_management/core/key_management_service.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_factory.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp" +#include "score/crypto/daemon/provider/handler/i_crypto_handler_factory.hpp" +#include "score/result/result.h" + +#include + +namespace score::crypto::daemon::provider::score_provider::operations::factory +{ + +/// @brief Abstract base handler factory for the score interface family. +/// +/// Implements the daemon's ICryptoHandlerFactory by dispatching CreateHandler +/// requests to protected virtual factory methods. Concrete score providers +/// (e.g. OpenSSL) inherit and override the factory methods to create their +/// provider-specific handlers. +/// +/// Default factory methods return kUnsupportedOperation so that a provider +/// need only implement the operations it supports. +class ScoreHandlerFactory : public handler::ICryptoHandlerFactory +{ + public: + ScoreHandlerFactory(std::shared_ptr key_factory, + std::shared_ptr slot_handler, + key_management::KeyManagementService::Sptr km_service); + + ~ScoreHandlerFactory() override = default; + + /// Routes to CreateHashHandler, CreateMacHandler, or CreateKeyManagementHandler. + ::score::Result CreateHandler(const common::HandlerId& handlerId, + const common::AlgorithmId& algorithm) override; + + protected: + /// Override in concrete provider to create a hash handler. Default returns unsupported. + [[nodiscard]] virtual ::score::Result CreateHashHandler( + const common::AlgorithmId& algorithm); + + /// Override in concrete provider to create a MAC handler. Default returns unsupported. + [[nodiscard]] virtual ::score::Result CreateMacHandler( + const common::AlgorithmId& algorithm); + + /// Override in concrete provider to create a key management handler. Default returns unsupported. + [[nodiscard]] virtual ::score::Result CreateKeyManagementHandler(); + + std::shared_ptr m_key_factory; + std::shared_ptr m_slot_handler; + key_management::KeyManagementService::Sptr m_km_service; + + private: + static constexpr const char* HASH = "HASH"; + static constexpr const char* MAC = "MAC"; + static constexpr const char* KEY_MANAGEMENT = "KEY_MANAGEMENT"; +}; + +} // namespace score::crypto::daemon::provider::score_provider::operations::factory + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_FACTORY_SCORE_HANDLER_FACTORY_HPP diff --git a/score/crypto/daemon/provider/score_provider/operations/factory/src/score_handler_factory.cpp b/score/crypto/daemon/provider/score_provider/operations/factory/src/score_handler_factory.cpp new file mode 100644 index 0000000..30df705 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/factory/src/score_handler_factory.cpp @@ -0,0 +1,83 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/operations/factory/score_handler_factory.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/result/result.h" + +namespace score::crypto::daemon::provider::score_provider::operations::factory +{ + +ScoreHandlerFactory::ScoreHandlerFactory(std::shared_ptr key_factory, + std::shared_ptr slot_handler, + key_management::KeyManagementService::Sptr km_service) + : m_key_factory{std::move(key_factory)}, + m_slot_handler{std::move(slot_handler)}, + m_km_service{std::move(km_service)} +{ +} + +::score::Result ScoreHandlerFactory::CreateHandler(const common::HandlerId& handlerId, + const common::AlgorithmId& algorithm) +{ + if (handlerId == HASH) + { + return CreateHashHandler(algorithm); + } + if (handlerId == MAC) + { + return CreateMacHandler(algorithm); + } + if (handlerId == KEY_MANAGEMENT) + { + return CreateKeyManagementHandler(); + } + + ::score::result::Error error( + static_cast<::score::result::ErrorCode>(::score::mw::crypto::CryptoErrorCode::kUnsupportedOperation), + ::score::mw::crypto::kCryptoErrorDomain, + "Handler not supported: " + handlerId); + return ::score::Result(::score::unexpect, error); +} + +// --------------------------------------------------------------------------- +// Default implementations — return unsupported +// --------------------------------------------------------------------------- + +::score::Result ScoreHandlerFactory::CreateHashHandler(const common::AlgorithmId& /*algorithm*/) +{ + ::score::result::Error error( + static_cast<::score::result::ErrorCode>(::score::mw::crypto::CryptoErrorCode::kUnsupportedOperation), + ::score::mw::crypto::kCryptoErrorDomain, + "Hash handler not supported by this score provider"); + return ::score::Result(::score::unexpect, error); +} + +::score::Result ScoreHandlerFactory::CreateMacHandler(const common::AlgorithmId& /*algorithm*/) +{ + ::score::result::Error error( + static_cast<::score::result::ErrorCode>(::score::mw::crypto::CryptoErrorCode::kUnsupportedOperation), + ::score::mw::crypto::kCryptoErrorDomain, + "MAC handler not supported by this score provider"); + return ::score::Result(::score::unexpect, error); +} + +::score::Result ScoreHandlerFactory::CreateKeyManagementHandler() +{ + ::score::result::Error error( + static_cast<::score::result::ErrorCode>(::score::mw::crypto::CryptoErrorCode::kUnsupportedOperation), + ::score::mw::crypto::kCryptoErrorDomain, + "Key management handler not supported by this score provider"); + return ::score::Result(::score::unexpect, error); +} + +} // namespace score::crypto::daemon::provider::score_provider::operations::factory diff --git a/score/crypto/daemon/provider/score_provider/operations/hash/BUILD b/score/crypto/daemon/provider/score_provider/operations/hash/BUILD new file mode 100644 index 0000000..6afc2ab --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/hash/BUILD @@ -0,0 +1,34 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +# Hash executor + abstract base hash handler for the score interface family. +cc_library( + name = "score_hash_handler", + srcs = [ + "src/hash_executor.cpp", + "src/score_hash_handler.cpp", + ], + hdrs = [ + "hash_executor.hpp", + "score_hash_handler.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/daemon/common", + "//score/crypto/daemon/common:algorithm_info", + "//score/crypto/daemon/provider/handler:handler_headers", + "//score/crypto/daemon/provider/handler:handler_utils_impl", + "//score/crypto/daemon/provider/handler:hash_handler_operations", + ], +) diff --git a/score/crypto/daemon/provider/score_provider/operations/hash/hash_executor.hpp b/score/crypto/daemon/provider/score_provider/operations/hash/hash_executor.hpp new file mode 100644 index 0000000..9725da2 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/hash/hash_executor.hpp @@ -0,0 +1,73 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_HASH_HASH_EXECUTOR_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_HASH_HASH_EXECUTOR_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" + +#include + +namespace score::crypto::daemon::provider::score_provider::operations::hash +{ + +class ScoreHashHandler; + +/// @brief Stateless executor implementing orchestration and visitor pattern for hash +/// operations under the score interface family. +/// +/// Responsibilities: +/// - Orchestrates operation flow and validates state transitions +/// - Routes operations to ScoreHashHandler typed methods via visitor pattern +/// - Decouples operation invocation from handler implementation +class HashExecutor +{ + public: + /// @brief Execute a hash operation on the given handler. + [[nodiscard]] Expected Execute( + ScoreHashHandler& handler, + const common::OperationIdentifier& operationId, + common::RequestParameters& request); + + private: + [[nodiscard]] Expected ExecuteStart(ScoreHashHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected ExecuteUpdate(ScoreHashHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected ExecuteFinish( + ScoreHashHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected ExecuteSingleShot( + ScoreHashHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected ExecuteReset(ScoreHashHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected GetDigestSize( + const ScoreHashHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] static Expected ValidateStreamTransition( + common::OperationAction action, + common::StreamOperationState currentState, + common::StreamOperationState& nextState); +}; + +} // namespace score::crypto::daemon::provider::score_provider::operations::hash + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_HASH_HASH_EXECUTOR_HPP diff --git a/score/crypto/daemon/provider/score_provider/operations/hash/score_hash_handler.hpp b/score/crypto/daemon/provider/score_provider/operations/hash/score_hash_handler.hpp new file mode 100644 index 0000000..27c0a09 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/hash/score_hash_handler.hpp @@ -0,0 +1,126 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_HASH_SCORE_HASH_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_HASH_SCORE_HASH_HANDLER_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/algorithm_info.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/handler/i_handler.hpp" + +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::score_provider::operations::hash +{ + +class HashExecutor; + +/// @brief Abstract base handler for hash operations under the score interface family. +/// +/// Implements the daemon's Handler interface by delegating Execute() to the +/// injected HashExecutor. Concrete score-interface providers (e.g. OpenSSL) +/// inherit from this class and override the typed hash methods. +/// +/// Typed methods default to kUnsupportedOperation so that a partially-implemented +/// provider compiles and returns a clear error at runtime. +/// +/// State management (algorithm, stream operation state) is centralised here. +class ScoreHashHandler : public handler::Handler +{ + public: + using Sptr = std::shared_ptr; + + ScoreHashHandler() = delete; + + /// @param executor Hash executor injected by the handler factory. + /// @param algorithm Algorithm identifier (e.g. "SHA256"). + explicit ScoreHashHandler(std::unique_ptr executor, const common::AlgorithmId& algorithm); + + ~ScoreHashHandler() override = default; + + // ----------------------------------------------------------------------- + // Handler interface (final — orchestration is fixed in the base) + // ----------------------------------------------------------------------- + + /// Delegates to the injected executor. + [[nodiscard]] Expected Execute( + const common::OperationIdentifier& operationId, + common::RequestParameters& request) override; + + /// Validates algorithm and resets state to IDLE. + [[nodiscard]] Expected InitializeContext( + const handler::InitializationParams& init_params) override; + + /// Resets intermediate state back to IDLE. + [[nodiscard]] Expected Reset() override; + + // ----------------------------------------------------------------------- + // Stream state management + // ----------------------------------------------------------------------- + + [[nodiscard]] common::StreamOperationState GetOperationState() const noexcept + { + return m_state; + } + + void SetOperationState(common::StreamOperationState state) noexcept + { + m_state = state; + } + + [[nodiscard]] const common::AlgorithmId& GetAlgorithm() const noexcept + { + return m_algorithm; + } + + // ----------------------------------------------------------------------- + // Typed hash operations — override in concrete provider handlers + // ----------------------------------------------------------------------- + + /// @brief Start/restart a hash operation on an existing initialized context. + [[nodiscard]] virtual Expected StartHash( + const std::optional initialDataOrIV); + + /// @brief Add data to the active hash stream. + [[nodiscard]] virtual Expected UpdateHash( + const common::RequestParameter& dataToHash); + + /// @brief Finalize the hash and produce the digest. + [[nodiscard]] virtual Expected FinalizeHash( + std::optional hashOutput, + const std::optional finalDataToHash); + + /// @brief Perform single-shot hash without streaming. + [[nodiscard]] virtual Expected SingleShotHash( + const common::RequestParameter& dataToHash, + std::optional outputHash, + std::optional iv); + + /// @brief Get the digest size for the current algorithm. + [[nodiscard]] virtual Expected GetDigestSize() const; + + protected: + common::AlgorithmId m_algorithm; + common::StreamOperationState m_state{common::StreamOperationState::IDLE}; + + private: + std::unique_ptr m_executor; +}; + +} // namespace score::crypto::daemon::provider::score_provider::operations::hash + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_HASH_SCORE_HASH_HANDLER_HPP diff --git a/score/crypto/daemon/provider/score_provider/operations/hash/src/hash_executor.cpp b/score/crypto/daemon/provider/score_provider/operations/hash/src/hash_executor.cpp new file mode 100644 index 0000000..4d8f33a --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/hash/src/hash_executor.cpp @@ -0,0 +1,227 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/operations/hash/hash_executor.hpp" +#include "score/crypto/daemon/provider/handler/operations/hash_handler_operations.hpp" +#include "score/crypto/daemon/provider/handler/src/handler_utils.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/hash/score_hash_handler.hpp" + +namespace score::crypto::daemon::provider::score_provider::operations::hash +{ + +namespace handler = ::score::crypto::daemon::provider::handler; +using common::DaemonErrorCode; +using common::RequestParameters; +using common::ResponseParameters; +using common::StreamOperationState; + +Expected HashExecutor::Execute(ScoreHashHandler& handler, + const common::OperationIdentifier& operationId, + RequestParameters& request) +{ + if (operationId.operationAction == handler::hash_handler_operations::HASH_GET_DIGEST_SIZE) + { + return GetDigestSize(handler, request); + } + + if (operationId.operationAction == handler::hash_handler_operations::HASH_RESET) + { + auto result = ExecuteReset(handler, request); + if (!result.has_value()) + { + return make_unexpected(result.error()); + } + return ResponseParameters{}; + } + + if (operationId.operationAction == handler::hash_handler_operations::HASH_SS) + { + StreamOperationState state = handler.GetOperationState(); + if (state != StreamOperationState::IDLE) + { + return make_unexpected(DaemonErrorCode::kOperationInProgress); + } + return ExecuteSingleShot(handler, request); + } + + // Streaming operations: validate state machine transition + StreamOperationState currentState = handler.GetOperationState(); + StreamOperationState nextState = StreamOperationState::IDLE; + const auto sequenceValidation = ValidateStreamTransition(operationId.operationAction, currentState, nextState); + if (!sequenceValidation.has_value()) + { + return make_unexpected(sequenceValidation.error()); + } + + if (operationId.operationAction == handler::hash_handler_operations::HASH_FINISH) + { + auto result = ExecuteFinish(handler, request); + if (result.has_value()) + { + handler.SetOperationState(nextState); + } + return result; + } + + const auto result = [&]() -> Expected { + if (operationId.operationAction == handler::hash_handler_operations::HASH_INIT) + { + return ExecuteStart(handler, request); + } + if (operationId.operationAction == handler::hash_handler_operations::HASH_UPDATE) + { + return ExecuteUpdate(handler, request); + } + return make_unexpected(DaemonErrorCode::kInvalidOperation); + }(); + + if (result.has_value()) + { + handler.SetOperationState(nextState); + } + else + { + return make_unexpected(result.error()); + } + + return ResponseParameters{}; +} + +Expected HashExecutor::ExecuteStart(ScoreHashHandler& handler, + RequestParameters& request) +{ + std::optional initialDataOrIV; + if (!request.empty()) + { + if (auto* buf = std::get_if(&request[0])) + { + initialDataOrIV.emplace(*buf); + } + } + return handler.StartHash(initialDataOrIV); +} + +Expected HashExecutor::ExecuteUpdate(ScoreHashHandler& handler, + RequestParameters& request) +{ + if (request.empty()) + { + return make_unexpected(DaemonErrorCode::kInsufficientParameters); + } + + auto* buf = std::get_if(&request[0]); + if (buf == nullptr) + { + return make_unexpected(DaemonErrorCode::kInvalidDataType); + } + + return handler.UpdateHash(*buf); +} + +Expected HashExecutor::ExecuteFinish(ScoreHashHandler& handler, + RequestParameters& request) +{ + std::optional output; + if (!request.empty()) + { + if (auto* buf = std::get_if(&request[0])) + { + output.emplace(*buf); + } + } + + std::optional finalData; + if (request.size() > 1) + { + if (auto* buf = std::get_if(&request[1])) + { + finalData.emplace(*buf); + } + } + + return handler.FinalizeHash(output, finalData); +} + +Expected HashExecutor::ExecuteSingleShot(ScoreHashHandler& handler, + RequestParameters& request) +{ + if (request.empty()) + { + return make_unexpected(DaemonErrorCode::kInsufficientParameters); + } + + auto* data = std::get_if(&request[0]); + if (data == nullptr) + { + return make_unexpected(DaemonErrorCode::kInvalidDataType); + } + + std::optional output; + if (request.size() > 1) + { + if (auto* buf = std::get_if(&request[1])) + { + output.emplace(*buf); + } + } + + std::optional iv; + if (request.size() > 2) + { + if (auto* buf = std::get_if(&request[2])) + { + iv.emplace(*buf); + } + } + + return handler.SingleShotHash(*data, output, iv); +} + +Expected HashExecutor::ExecuteReset(ScoreHashHandler& handler, + RequestParameters& /*request*/) +{ + return handler.Reset(); +} + +Expected HashExecutor::GetDigestSize(const ScoreHashHandler& handler, + RequestParameters& /*request*/) +{ + return handler.GetDigestSize(); +} + +// static +Expected HashExecutor::ValidateStreamTransition( + const common::OperationAction action, + const StreamOperationState currentState, + StreamOperationState& nextState) +{ + std::string_view opId; + if (action == handler::hash_handler_operations::HASH_INIT) + { + opId = "START"; + } + else if (action == handler::hash_handler_operations::HASH_UPDATE) + { + opId = "UPDATE"; + } + else if (action == handler::hash_handler_operations::HASH_FINISH) + { + opId = "FINISH"; + } + else + { + return make_unexpected(DaemonErrorCode::kInvalidOperation); + } + return handler::handler_utils::ValidateStreamOperationSequence(currentState, opId, nextState); +} + +} // namespace score::crypto::daemon::provider::score_provider::operations::hash diff --git a/score/crypto/daemon/provider/score_provider/operations/hash/src/score_hash_handler.cpp b/score/crypto/daemon/provider/score_provider/operations/hash/src/score_hash_handler.cpp new file mode 100644 index 0000000..2574daf --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/hash/src/score_hash_handler.cpp @@ -0,0 +1,85 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/operations/hash/score_hash_handler.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/hash/hash_executor.hpp" + +namespace score::crypto::daemon::provider::score_provider::operations::hash +{ + +using common::DaemonErrorCode; +using common::ResponseParameters; +using common::StreamOperationState; + +ScoreHashHandler::ScoreHashHandler(std::unique_ptr executor, const common::AlgorithmId& algorithm) + : m_algorithm{algorithm}, m_state{StreamOperationState::IDLE}, m_executor{std::move(executor)} +{ +} + +Expected ScoreHashHandler::Execute(const common::OperationIdentifier& operationId, + common::RequestParameters& request) +{ + return m_executor->Execute(*this, operationId, request); +} + +Expected ScoreHashHandler::InitializeContext( + const handler::InitializationParams& /*init_params*/) +{ + m_state = StreamOperationState::IDLE; + return std::monostate{}; +} + +Expected ScoreHashHandler::Reset() +{ + m_state = StreamOperationState::IDLE; + return std::monostate{}; +} + +// --------------------------------------------------------------------------- +// Default typed operations — return unsupported unless overridden +// --------------------------------------------------------------------------- + +Expected ScoreHashHandler::StartHash( + const std::optional /*initialDataOrIV*/) +{ + return make_unexpected(DaemonErrorCode::kUnsupportedOperation); +} + +Expected ScoreHashHandler::UpdateHash(const common::RequestParameter& /*dataToHash*/) +{ + return make_unexpected(DaemonErrorCode::kUnsupportedOperation); +} + +Expected ScoreHashHandler::FinalizeHash( + std::optional /*hashOutput*/, + const std::optional /*finalDataToHash*/) +{ + return make_unexpected(DaemonErrorCode::kUnsupportedOperation); +} + +Expected ScoreHashHandler::SingleShotHash( + const common::RequestParameter& /*dataToHash*/, + std::optional /*outputHash*/, + std::optional /*iv*/) +{ + return make_unexpected(DaemonErrorCode::kUnsupportedOperation); +} + +Expected ScoreHashHandler::GetDigestSize() const +{ + const auto size = common::LookupDigestSize(std::string_view{m_algorithm.data(), m_algorithm.size()}); + ResponseParameters response; + response.push_back(size.value_or(64U)); + return response; +} + +} // namespace score::crypto::daemon::provider::score_provider::operations::hash diff --git a/score/crypto/daemon/provider/score_provider/operations/key_management/BUILD b/score/crypto/daemon/provider/score_provider/operations/key_management/BUILD new file mode 100644 index 0000000..f764009 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/key_management/BUILD @@ -0,0 +1,25 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +# Abstract base key management handler for the score interface family. +cc_library( + name = "score_key_management_handler", + srcs = ["src/score_key_management_handler.cpp"], + hdrs = ["score_key_management_handler.hpp"], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/daemon/provider/executors:key_mgmt_executor", + "//score/crypto/daemon/provider/handler:handler_headers", + ], +) diff --git a/score/crypto/daemon/provider/score_provider/operations/key_management/score_key_management_handler.hpp b/score/crypto/daemon/provider/score_provider/operations/key_management/score_key_management_handler.hpp new file mode 100644 index 0000000..2937ca9 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/key_management/score_key_management_handler.hpp @@ -0,0 +1,62 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_KEY_MANAGEMENT_SCORE_KEY_MANAGEMENT_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_KEY_MANAGEMENT_SCORE_KEY_MANAGEMENT_HANDLER_HPP + +#include "score/crypto/daemon/provider/executors/key_mgmt_context.hpp" +#include "score/crypto/daemon/provider/executors/key_mgmt_executor.hpp" +#include "score/crypto/daemon/provider/handler/i_handler.hpp" + +#include + +namespace score::crypto::daemon::provider::score_provider::operations::key_management +{ + +/// @brief Abstract base handler for key management operations under the score +/// interface family. +/// +/// Acts as the bridge between the daemon dispatch interface (Handler) and the +/// shared KeyManagementExecutor. All collaborators are constructor-injected. +/// InitializeContext() stores the per-context runtime identity; Execute() +/// delegates to the executor. +class ScoreKeyManagementHandler : public handler::Handler +{ + public: + explicit ScoreKeyManagementHandler(std::unique_ptr executor); + + ~ScoreKeyManagementHandler() override = default; + + ScoreKeyManagementHandler(const ScoreKeyManagementHandler&) = delete; + ScoreKeyManagementHandler& operator=(const ScoreKeyManagementHandler&) = delete; + ScoreKeyManagementHandler(ScoreKeyManagementHandler&&) = delete; + ScoreKeyManagementHandler& operator=(ScoreKeyManagementHandler&&) = delete; + + /// Stores the runtime context (client_id, context_node_id, provider_id). + [[nodiscard]] Expected InitializeContext( + const handler::InitializationParams& init_params) override; + + /// Delegates to KeyManagementExecutor::Execute. + [[nodiscard]] Expected Execute( + const common::OperationIdentifier& operationId, + common::RequestParameters& request) override; + + [[nodiscard]] Expected Reset() override; + + protected: + std::unique_ptr m_executor; + crypto_executor::KeyMgmtExecutionContext m_ctx; +}; + +} // namespace score::crypto::daemon::provider::score_provider::operations::key_management + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_KEY_MANAGEMENT_SCORE_KEY_MANAGEMENT_HANDLER_HPP diff --git a/score/crypto/daemon/provider/score_provider/operations/key_management/src/score_key_management_handler.cpp b/score/crypto/daemon/provider/score_provider/operations/key_management/src/score_key_management_handler.cpp new file mode 100644 index 0000000..acbf042 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/key_management/src/score_key_management_handler.cpp @@ -0,0 +1,60 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/operations/key_management/score_key_management_handler.hpp" + +#include +#include + +namespace score::crypto::daemon::provider::score_provider::operations::key_management +{ + +namespace +{ +constexpr std::string_view LOG_PREFIX = "[SCORE_KEY_MGMT_HANDLER] "; +} + +ScoreKeyManagementHandler::ScoreKeyManagementHandler(std::unique_ptr executor) + : m_executor{std::move(executor)}, m_ctx{} +{ +} + +Expected ScoreKeyManagementHandler::InitializeContext( + const handler::InitializationParams& init_params) +{ + m_ctx.client_id = init_params.client_id; + m_ctx.context_node_id = init_params.context_node_id; + if (init_params.provider_id != common::kInvalidProviderId) + { + m_ctx.provider_id = init_params.provider_id; + } + return std::monostate{}; +} + +Expected ScoreKeyManagementHandler::Execute( + const common::OperationIdentifier& operationId, + common::RequestParameters& request) +{ + if (!m_executor) + { + std::cerr << LOG_PREFIX << "Execute: executor not injected\n"; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + return m_executor->Execute(m_ctx, operationId, request); +} + +Expected ScoreKeyManagementHandler::Reset() +{ + return std::monostate{}; +} + +} // namespace score::crypto::daemon::provider::score_provider::operations::key_management diff --git a/score/crypto/daemon/provider/score_provider/operations/mac/BUILD b/score/crypto/daemon/provider/score_provider/operations/mac/BUILD new file mode 100644 index 0000000..4098425 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/mac/BUILD @@ -0,0 +1,33 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +# MAC executor + abstract base MAC handler for the score interface family. +cc_library( + name = "score_mac_handler", + srcs = [ + "src/mac_executor.cpp", + "src/score_mac_handler.cpp", + ], + hdrs = [ + "mac_executor.hpp", + "score_mac_handler.hpp", + ], + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/daemon/common", + "//score/crypto/daemon/provider/handler:handler_headers", + "//score/crypto/daemon/provider/handler:handler_utils_impl", + "//score/crypto/daemon/provider/handler:mac_handler_operations", + ], +) diff --git a/score/crypto/daemon/provider/score_provider/operations/mac/mac_executor.hpp b/score/crypto/daemon/provider/score_provider/operations/mac/mac_executor.hpp new file mode 100644 index 0000000..2d9c963 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/mac/mac_executor.hpp @@ -0,0 +1,75 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_MAC_MAC_EXECUTOR_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_MAC_MAC_EXECUTOR_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" + +namespace score::crypto::daemon::provider::score_provider::operations::mac +{ + +class ScoreMacHandler; + +/// @brief Stateless executor implementing the strategy / visitor pattern for MAC +/// operations under the score interface family. +/// +/// Mirrors the HashExecutor pattern: +/// - Orchestrates operation flow and validates stream state transitions +/// - Extracts IPC buffer parameters from RequestParameters +/// - Routes operations to the typed ScoreMacHandler methods +/// - Packs results back into ResponseParameters +class MacExecutor +{ + public: + [[nodiscard]] Expected Execute( + ScoreMacHandler& handler, + const common::OperationIdentifier& operationId, + common::RequestParameters& request); + + private: + [[nodiscard]] Expected ExecuteInit(ScoreMacHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected ExecuteUpdate(ScoreMacHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected ExecuteFinalize( + ScoreMacHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected ExecuteVerify( + ScoreMacHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected ExecuteReset(ScoreMacHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected ExecuteSingleShot( + ScoreMacHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected GetMacSize( + const ScoreMacHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] static Expected ValidateStreamTransition( + common::OperationAction action, + common::StreamOperationState currentState, + common::StreamOperationState& nextState); +}; + +} // namespace score::crypto::daemon::provider::score_provider::operations::mac + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_MAC_MAC_EXECUTOR_HPP diff --git a/score/crypto/daemon/provider/score_provider/operations/mac/score_mac_handler.hpp b/score/crypto/daemon/provider/score_provider/operations/mac/score_mac_handler.hpp new file mode 100644 index 0000000..a8ea94f --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/mac/score_mac_handler.hpp @@ -0,0 +1,122 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_MAC_SCORE_MAC_HANDLER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_MAC_SCORE_MAC_HANDLER_HPP + +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/provider/handler/i_handler.hpp" + +#include +#include +#include +#include + +namespace score::crypto::daemon::provider::score_provider::operations::mac +{ + +class MacExecutor; + +/// @brief Abstract base handler for MAC operations under the score interface family. +/// +/// Implements the daemon's Handler interface by delegating Execute() to the +/// injected MacExecutor. Concrete score-interface providers (e.g. OpenSSL) +/// inherit from this class and override the typed MAC methods. +/// +/// Typed methods default to kUnsupportedOperation so that a partially-implemented +/// provider compiles and returns a clear error at runtime. +/// +/// Key binding is performed via InitializeContext(): the mediator sets +/// bound_key_handler before calling InitializeContext(), which the concrete +/// handler uses to bind the key. +class ScoreMacHandler : public handler::Handler +{ + public: + using Sptr = std::shared_ptr; + + ScoreMacHandler() = delete; + + /// @param executor MAC executor injected by the handler factory. + /// @param algorithm Algorithm identifier (e.g. "HMAC-SHA256"). + explicit ScoreMacHandler(std::unique_ptr executor, const common::AlgorithmId& algorithm); + + ~ScoreMacHandler() override = default; + + // ----------------------------------------------------------------------- + // Handler interface + // ----------------------------------------------------------------------- + + [[nodiscard]] Expected Execute( + const common::OperationIdentifier& operationId, + common::RequestParameters& request) override; + + [[nodiscard]] Expected InitializeContext( + const handler::InitializationParams& init_params) override; + + [[nodiscard]] Expected Reset() override; + + // ----------------------------------------------------------------------- + // Stream state management + // ----------------------------------------------------------------------- + + [[nodiscard]] common::StreamOperationState GetOperationState() const noexcept + { + return m_state; + } + + void SetOperationState(common::StreamOperationState state) noexcept + { + m_state = state; + } + + [[nodiscard]] const common::AlgorithmId& GetAlgorithm() const noexcept + { + return m_algorithm; + } + + // ----------------------------------------------------------------------- + // Typed MAC operations — override in concrete provider handlers + // ----------------------------------------------------------------------- + + /// @brief Get the MAC tag size for the current algorithm. + [[nodiscard]] virtual std::size_t GetMacSize() const noexcept; + + /// @brief (Re-)initialize the MAC stream context with the cached key. + [[nodiscard]] virtual Expected StartMac( + const std::optional initialDataOrIV); + + /// @brief Add data to the active MAC stream. + [[nodiscard]] virtual Expected UpdateMac( + const common::RequestParameter& dataToMac); + + /// @brief Finalize the MAC and produce the tag. + [[nodiscard]] virtual Expected FinalizeMac( + std::optional macOutput, + const std::optional finalDataToMac); + + /// @brief Verify a MAC tag using constant-time comparison. + [[nodiscard]] virtual Expected VerifyMac( + const common::RequestParameter& expectedTag); + + protected: + common::AlgorithmId m_algorithm; + common::StreamOperationState m_state{common::StreamOperationState::IDLE}; + + private: + std::unique_ptr m_executor; +}; + +} // namespace score::crypto::daemon::provider::score_provider::operations::mac + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_OPERATIONS_MAC_SCORE_MAC_HANDLER_HPP diff --git a/score/crypto/daemon/provider/score_provider/operations/mac/src/mac_executor.cpp b/score/crypto/daemon/provider/score_provider/operations/mac/src/mac_executor.cpp new file mode 100644 index 0000000..37fa75e --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/mac/src/mac_executor.cpp @@ -0,0 +1,261 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/operations/mac/mac_executor.hpp" +#include "score/crypto/daemon/provider/handler/operations/mac_handler_operations.hpp" +#include "score/crypto/daemon/provider/handler/src/handler_utils.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/mac/score_mac_handler.hpp" + +namespace score::crypto::daemon::provider::score_provider::operations::mac +{ + +namespace handler = ::score::crypto::daemon::provider::handler; +using common::DaemonErrorCode; +using common::RequestParameters; +using common::ResponseParameters; +using common::StreamOperationState; + +// --------------------------------------------------------------------------- +// Public entry point +// --------------------------------------------------------------------------- + +Expected MacExecutor::Execute(ScoreMacHandler& handler, + const common::OperationIdentifier& operationId, + RequestParameters& request) +{ + if (operationId.operationAction == handler::mac_handler_operations::MAC_GET_SIZE) + { + return GetMacSize(handler, request); + } + + if (operationId.operationAction == handler::mac_handler_operations::MAC_RESET) + { + auto res = ExecuteReset(handler, request); + if (!res.has_value()) + { + return make_unexpected(res.error()); + } + return ResponseParameters{}; + } + + if (operationId.operationAction == handler::mac_handler_operations::MAC_SS) + { + if (handler.GetOperationState() == StreamOperationState::STREAM_ACTIVE) + { + return make_unexpected(DaemonErrorCode::kOperationInProgress); + } + auto result = ExecuteSingleShot(handler, request); + if (result.has_value()) + { + handler.SetOperationState(StreamOperationState::IDLE); + } + return result; + } + + if (operationId.operationAction == handler::mac_handler_operations::MAC_VERIFY) + { + if (handler.GetOperationState() == StreamOperationState::IDLE) + { + return make_unexpected(DaemonErrorCode::kStreamNotInitialized); + } + auto result = ExecuteVerify(handler, request); + if (result.has_value()) + { + handler.SetOperationState(StreamOperationState::IDLE); + } + return result; + } + + // Streaming operations: validate state machine transition + StreamOperationState currentState = handler.GetOperationState(); + StreamOperationState nextState = StreamOperationState::IDLE; + const auto validation = ValidateStreamTransition(operationId.operationAction, currentState, nextState); + if (!validation.has_value()) + { + return make_unexpected(validation.error()); + } + + if (operationId.operationAction == handler::mac_handler_operations::MAC_INIT) + { + auto result = ExecuteInit(handler, request); + if (result.has_value()) + { + handler.SetOperationState(nextState); + return ResponseParameters{}; + } + return make_unexpected(result.error()); + } + + if (operationId.operationAction == handler::mac_handler_operations::MAC_FINAL) + { + auto result = ExecuteFinalize(handler, request); + if (result.has_value()) + { + handler.SetOperationState(nextState); + } + return result; + } + + if (operationId.operationAction == handler::mac_handler_operations::MAC_UPDATE) + { + auto result = ExecuteUpdate(handler, request); + if (result.has_value()) + { + handler.SetOperationState(nextState); + return ResponseParameters{}; + } + return make_unexpected(result.error()); + } + + return make_unexpected(DaemonErrorCode::kInvalidOperation); +} + +// --------------------------------------------------------------------------- +// Operation implementations +// --------------------------------------------------------------------------- + +Expected MacExecutor::ExecuteInit(ScoreMacHandler& handler, RequestParameters& request) +{ + std::optional initialData; + if (!request.empty()) + { + initialData.emplace(request[0]); + } + return handler.StartMac(initialData); +} + +Expected MacExecutor::ExecuteUpdate(ScoreMacHandler& handler, + RequestParameters& request) +{ + if (request.empty()) + { + return make_unexpected(DaemonErrorCode::kInsufficientParameters); + } + return handler.UpdateMac(request[0]); +} + +Expected MacExecutor::ExecuteFinalize(ScoreMacHandler& handler, + RequestParameters& request) +{ + std::optional output; + if (!request.empty()) + { + if (auto* buf = std::get_if(&request[0])) + { + output.emplace(*buf); + } + } + + std::optional finalData; + if (request.size() > 1U) + { + finalData.emplace(request[1]); + } + + return handler.FinalizeMac(output, finalData); +} + +Expected MacExecutor::ExecuteVerify(ScoreMacHandler& handler, + RequestParameters& request) +{ + if (request.empty()) + { + return make_unexpected(DaemonErrorCode::kInsufficientParameters); + } + + auto res = handler.VerifyMac(request[0]); + if (!res.has_value()) + { + return make_unexpected(res.error()); + } + + ResponseParameters response; + response.push_back(res.value()); + return response; +} + +Expected MacExecutor::ExecuteSingleShot(ScoreMacHandler& handler, + RequestParameters& request) +{ + if (request.empty()) + { + return make_unexpected(DaemonErrorCode::kInsufficientParameters); + } + + std::optional output; + if (request.size() > 1U) + { + if (auto* buf = std::get_if(&request[1])) + { + output.emplace(*buf); + } + } + + auto init = handler.StartMac(std::nullopt); + if (!init.has_value()) + { + return make_unexpected(init.error()); + } + + auto up = handler.UpdateMac(request[0]); + if (!up.has_value()) + { + return make_unexpected(up.error()); + } + + return handler.FinalizeMac(output, std::nullopt); +} + +Expected MacExecutor::GetMacSize(const ScoreMacHandler& handler, + RequestParameters& /*request*/) +{ + ResponseParameters response; + response.push_back(static_cast(handler.GetMacSize())); + return response; +} + +Expected MacExecutor::ExecuteReset(ScoreMacHandler& handler, + RequestParameters& /*request*/) +{ + return handler.Reset(); +} + +// --------------------------------------------------------------------------- +// Stream state machine +// --------------------------------------------------------------------------- + +// static +Expected MacExecutor::ValidateStreamTransition(const common::OperationAction action, + const StreamOperationState currentState, + StreamOperationState& nextState) +{ + std::string_view opId; + if (action == handler::mac_handler_operations::MAC_INIT) + { + opId = "START"; + } + else if (action == handler::mac_handler_operations::MAC_UPDATE) + { + opId = "UPDATE"; + } + else if (action == handler::mac_handler_operations::MAC_FINAL) + { + opId = "FINISH"; + } + else + { + return make_unexpected(DaemonErrorCode::kInvalidOperation); + } + return handler::handler_utils::ValidateStreamOperationSequence(currentState, opId, nextState); +} + +} // namespace score::crypto::daemon::provider::score_provider::operations::mac diff --git a/score/crypto/daemon/provider/score_provider/operations/mac/src/score_mac_handler.cpp b/score/crypto/daemon/provider/score_provider/operations/mac/src/score_mac_handler.cpp new file mode 100644 index 0000000..fa2256b --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/mac/src/score_mac_handler.cpp @@ -0,0 +1,79 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/operations/mac/score_mac_handler.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/mac/mac_executor.hpp" + +namespace score::crypto::daemon::provider::score_provider::operations::mac +{ + +using common::DaemonErrorCode; +using common::ResponseParameters; +using common::StreamOperationState; + +ScoreMacHandler::ScoreMacHandler(std::unique_ptr executor, const common::AlgorithmId& algorithm) + : m_algorithm{algorithm}, m_state{StreamOperationState::IDLE}, m_executor{std::move(executor)} +{ +} + +Expected ScoreMacHandler::Execute(const common::OperationIdentifier& operationId, + common::RequestParameters& request) +{ + return m_executor->Execute(*this, operationId, request); +} + +Expected ScoreMacHandler::InitializeContext( + const handler::InitializationParams& /*init_params*/) +{ + m_state = StreamOperationState::IDLE; + return std::monostate{}; +} + +Expected ScoreMacHandler::Reset() +{ + m_state = StreamOperationState::IDLE; + return std::monostate{}; +} + +// --------------------------------------------------------------------------- +// Default typed operations — return unsupported unless overridden +// --------------------------------------------------------------------------- + +std::size_t ScoreMacHandler::GetMacSize() const noexcept +{ + return 0U; +} + +Expected ScoreMacHandler::StartMac( + const std::optional /*initialDataOrIV*/) +{ + return make_unexpected(DaemonErrorCode::kUnsupportedOperation); +} + +Expected ScoreMacHandler::UpdateMac(const common::RequestParameter& /*dataToMac*/) +{ + return make_unexpected(DaemonErrorCode::kUnsupportedOperation); +} + +Expected ScoreMacHandler::FinalizeMac( + std::optional /*macOutput*/, + const std::optional /*finalDataToMac*/) +{ + return make_unexpected(DaemonErrorCode::kUnsupportedOperation); +} + +Expected ScoreMacHandler::VerifyMac(const common::RequestParameter& /*expectedTag*/) +{ + return make_unexpected(DaemonErrorCode::kUnsupportedOperation); +} + +} // namespace score::crypto::daemon::provider::score_provider::operations::mac diff --git a/score/crypto/daemon/provider/score_provider/score_provider.hpp b/score/crypto/daemon/provider/score_provider/score_provider.hpp new file mode 100644 index 0000000..230b0bd --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/score_provider.hpp @@ -0,0 +1,66 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_SCORE_PROVIDER_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_SCORE_PROVIDER_HPP + +#include "score/crypto/daemon/provider/handler/i_crypto_handler_factory.hpp" +#include "score/crypto/daemon/provider/i_provider.hpp" + +#include + +namespace score::crypto::daemon::provider::score_provider +{ + +/// @brief Abstract base class for providers under the score interface family. +/// +/// Provides default lifecycle implementations (Initialize, Shutdown, getters) +/// and lazy handler factory creation via the CreateHandlerFactory() hook. +/// Concrete providers (e.g. OpenSSL) inherit and override as needed. +/// +/// Key management methods default to nullptr / no-op. A concrete provider +/// overrides them if it supports key operations. +class ScoreProvider : public IProvider +{ + public: + ScoreProvider() = default; + ~ScoreProvider() override = default; + + ScoreProvider(const ScoreProvider&) = delete; + ScoreProvider& operator=(const ScoreProvider&) = delete; + ScoreProvider(ScoreProvider&&) = delete; + ScoreProvider& operator=(ScoreProvider&&) = delete; + + // --- IProvider lifecycle --- + bool Initialize(const ProviderInitContext& ctx) override; + void Shutdown() override; + common::ProviderId GetProviderId() const override; + const common::ProviderName& GetProviderName() const override; + + /// Lazy-creates the handler factory via CreateHandlerFactory(). + std::shared_ptr GetCryptoHandlerFactory() override; + + protected: + /// Override in concrete provider to construct the provider-specific handler factory. + [[nodiscard]] virtual std::shared_ptr CreateHandlerFactory() = 0; + + bool m_initialized{false}; + common::ProviderId m_numeric_id{common::kInvalidProviderId}; + common::ProviderName m_provider_name{}; + + private: + handler::ICryptoHandlerFactory::Sptr m_handler_factory; +}; + +} // namespace score::crypto::daemon::provider::score_provider + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_SCORE_PROVIDER_HPP diff --git a/score/crypto/daemon/provider/score_provider/score_provider_config.hpp b/score/crypto/daemon/provider/score_provider/score_provider_config.hpp new file mode 100644 index 0000000..dcc8b9d --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/score_provider_config.hpp @@ -0,0 +1,91 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_CONFIG_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_CONFIG_HPP + +#include +#include + +namespace score::crypto::daemon::provider::score_provider +{ + +// Forward declaration — Configure() is implemented in score_provider_config.cpp +// to keep this header free of factory internals. +class ScoreProviderFactory; + +/// @brief Plain-data configuration entry for one score-interface provider. +/// +/// All fields are standard-library types so this struct is usable by both the +/// generic daemon config reader (JSON / flatbuffer) and the bootstrapper +/// without pulling in any provider-internal headers. +struct ScoreProviderEntry +{ + /// Provider name used to register and look up this provider in ProviderManager. + std::string providerName{}; + /// Implementation tag that selects the concrete factory, e.g. "openssl". + std::string providerImpl{}; +}; + +/// @brief Aggregates the ordered list of score-interface provider entries for the daemon. +/// +/// This is the canonical score-provider configuration type. The daemon's +/// top-level Config class holds one instance (via a type alias in the config +/// namespace) so that config.hpp does not need to define provider structures. +/// +/// @par Visitor pattern +/// Configure() acts as a "visitor" that pushes the provider entries into a +/// ScoreProviderFactory. The conversion from ScoreProviderEntry to internal +/// factory configuration lives entirely within the score_provider subsystem. +/// Typical bootstrapper usage: +/// @code +/// config.GetScoreProviderConfig().PopulateDefaults(); +/// auto factory = std::make_unique(); +/// config.GetScoreProviderConfig().Configure(*factory); +/// provider_manager->RegisterFactory(std::move(factory)); +/// @endcode +class ScoreProviderConfig +{ + public: + ScoreProviderConfig() = default; + + /// @brief Add a provider entry (called by parser or bootstrapper). + void AddProviderEntry(ScoreProviderEntry entry) + { + m_providers.push_back(std::move(entry)); + } + + /// @brief Get all registered provider entries (read-only). + const std::vector& GetProviderEntries() const + { + return m_providers; + } + + /// @brief Populate production default provider entries when no config was loaded. + /// + /// Adds an OpenSSL software entry with standard defaults. No-op if any + /// provider entries are already present (e.g. loaded from file or test fixture). + void PopulateDefaults(); + + /// @brief Visit @p factory: convert each provider entry and configure the factory. + /// + /// Implemented out-of-line in score_provider_config.cpp so that this header + /// remains free of factory internals. + void Configure(ScoreProviderFactory& factory) const; + + private: + std::vector m_providers; +}; + +} // namespace score::crypto::daemon::provider::score_provider + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_CONFIG_HPP diff --git a/score/crypto/daemon/provider/score_provider/score_provider_factory.hpp b/score/crypto/daemon/provider/score_provider/score_provider_factory.hpp new file mode 100644 index 0000000..830cc31 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/score_provider_factory.hpp @@ -0,0 +1,68 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_SCORE_PROVIDER_FACTORY_HPP +#define SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_SCORE_PROVIDER_FACTORY_HPP + +#include "score/crypto/daemon/provider/i_provider_factory.hpp" +#include "score/crypto/daemon/provider/score_provider/score_provider_config.hpp" + +#include + +namespace score::crypto::daemon::provider::score_provider +{ + +/// @brief Top-level factory for the score interface family. +/// +/// Mirrors the Pkcs11ProviderFactory pattern: accepts a vector of configuration +/// entries, each describing a concrete score-interface provider to create. +/// CreateAndRegister() iterates the entries and delegates to the respective +/// internal provider factory (e.g. OpenSSLProviderFactory). +/// +/// Configuration is supplied externally via SetConfigs() (the acceptor side of +/// the ScoreProviderConfig visitor pattern) or the explicit vector constructor. +/// The daemon bootstrapper delegates config setup to ScoreProviderConfig::Configure(): +/// +/// @code +/// config.GetScoreProviderConfig().PopulateDefaults(); +/// auto factory = std::make_unique(); +/// config.GetScoreProviderConfig().Configure(*factory); +/// provider_manager->RegisterFactory(std::move(factory)); +/// @endcode +class ScoreProviderFactory final : public IProviderFactory +{ + public: + /// Construct with default (empty) configuration. + ScoreProviderFactory() = default; + + /// Construct with externally supplied provider configurations. + explicit ScoreProviderFactory(std::vector configs); + + ~ScoreProviderFactory() override = default; + + /// @brief Accept a provider-config vector pushed by ScoreProviderConfig::Configure(). + /// + /// This is the "acceptor" side of the visitor pattern: ScoreProviderConfig + /// (the visitor) converts its ScoreProviderEntry list and hands them to + /// the factory via this method. + void SetConfigs(std::vector configs); + + /// Creates and registers all configured score providers. + bool CreateAndRegister(ProviderManager& manager) override; + + private: + std::vector m_configs; +}; + +} // namespace score::crypto::daemon::provider::score_provider + +#endif // SCORE_CRYPTO_DAEMON_PROVIDER_SCORE_PROVIDER_SCORE_PROVIDER_FACTORY_HPP diff --git a/score/crypto/daemon/provider/score_provider/src/score_provider.cpp b/score/crypto/daemon/provider/score_provider/src/score_provider.cpp new file mode 100644 index 0000000..19cef34 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/src/score_provider.cpp @@ -0,0 +1,64 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/score_provider.hpp" + +#include + +namespace score::crypto::daemon::provider::score_provider +{ + +bool ScoreProvider::Initialize(const ProviderInitContext& ctx) +{ + if (m_initialized) + { + return true; + } + + m_numeric_id = ctx.numeric_id; + m_provider_name = ctx.name; + m_initialized = true; + + std::cout << "[ScoreProvider] Initialized (ID: " << m_numeric_id << ", Name: " << m_provider_name << ")\n"; + return true; +} + +void ScoreProvider::Shutdown() +{ + if (!m_initialized) + { + return; + } + m_handler_factory.reset(); + m_initialized = false; +} + +common::ProviderId ScoreProvider::GetProviderId() const +{ + return m_numeric_id; +} + +const common::ProviderName& ScoreProvider::GetProviderName() const +{ + return m_provider_name; +} + +std::shared_ptr ScoreProvider::GetCryptoHandlerFactory() +{ + if (!m_handler_factory) + { + m_handler_factory = CreateHandlerFactory(); + } + return m_handler_factory; +} + +} // namespace score::crypto::daemon::provider::score_provider diff --git a/score/crypto/daemon/provider/score_provider/src/score_provider_config.cpp b/score/crypto/daemon/provider/score_provider/src/score_provider_config.cpp new file mode 100644 index 0000000..0979f58 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/src/score_provider_config.cpp @@ -0,0 +1,37 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/score_provider_config.hpp" + +#include "score/crypto/daemon/provider/score_provider/score_provider_factory.hpp" + +namespace score::crypto::daemon::provider::score_provider +{ + +void ScoreProviderConfig::PopulateDefaults() +{ + if (!m_providers.empty()) + { + return; // Entries already present (from config file or test fixture). + } + ScoreProviderEntry openssl{}; + openssl.providerName = "OPENSSL"; + openssl.providerImpl = "openssl"; + m_providers.push_back(std::move(openssl)); +} + +void ScoreProviderConfig::Configure(ScoreProviderFactory& factory) const +{ + factory.SetConfigs(m_providers); +} + +} // namespace score::crypto::daemon::provider::score_provider diff --git a/score/crypto/daemon/provider/score_provider/src/score_provider_factory.cpp b/score/crypto/daemon/provider/score_provider/src/score_provider_factory.cpp new file mode 100644 index 0000000..9b2c18a --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/src/score_provider_factory.cpp @@ -0,0 +1,52 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/daemon/provider/score_provider/score_provider_factory.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/openssl_provider_factory.hpp" + +#include + +namespace score::crypto::daemon::provider::score_provider +{ + +ScoreProviderFactory::ScoreProviderFactory(std::vector configs) : m_configs{std::move(configs)} {} + +void ScoreProviderFactory::SetConfigs(std::vector configs) +{ + m_configs = std::move(configs); +} + +bool ScoreProviderFactory::CreateAndRegister(ProviderManager& manager) +{ + bool all_ok = true; + for (const auto& entry : m_configs) + { + if (entry.providerImpl == "openssl") + { + openssl::OpenSSLProviderFactory openssl_factory; + if (!openssl_factory.CreateAndRegister(manager)) + { + std::cerr << "[ScoreProviderFactory] Failed to create OpenSSL provider: " << entry.providerName << "\n"; + all_ok = false; + } + } + else + { + std::cerr << "[ScoreProviderFactory] Unknown provider implementation: " << entry.providerImpl << "\n"; + all_ok = false; + } + } + return all_ok; +} + +} // namespace score::crypto::daemon::provider::score_provider diff --git a/score/crypto/daemon/provider/src/provider_manager.cpp b/score/crypto/daemon/provider/src/provider_manager.cpp new file mode 100644 index 0000000..023553a --- /dev/null +++ b/score/crypto/daemon/provider/src/provider_manager.cpp @@ -0,0 +1,279 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include + +#include "score/crypto/daemon/provider/provider_manager.hpp" + +namespace score::crypto +{ +namespace daemon +{ +namespace provider +{ + +ProviderManager::ProviderManager(const score::crypto::daemon::config::Config& config) : m_config(config) {} + +ProviderManager::~ProviderManager() +{ + Shutdown(); + m_providers.clear(); + m_typeToProviderId.clear(); +} + +bool ProviderManager::Initialize() +{ + // Create and register all available providers + if (!CreateProviders()) + { + throw std::runtime_error("Failed to create providers"); + } + + // Use provided config or create default + config::ProviderInitConfig activeConfig = m_config.GetProviderInitConfig(); + + if (activeConfig.providers.empty()) + { + activeConfig = CreateDefaultConfig(); + } + + // Set the type-to-provider mappings + m_typeToProviderId = activeConfig.typeToProviderId; + + // Initialize all providers + return InitializeAll(); +} + +config::ProviderInitConfig ProviderManager::CreateDefaultConfig() +{ + config::ProviderInitConfig config; + + // Add all created providers to config as enabled + for (const auto& pair : m_providers) + { + config.AddProviderConfig(config::ProviderConfig(pair.second.numeric_id, pair.second.cryptoType, true)); + } + + // Set the first provider as default for all applicable types + if (!m_providers.empty()) + { + const auto& firstEntry = m_providers.begin()->second; + common::ProviderId firstProviderId = firstEntry.numeric_id; + + config.typeToProviderId = m_typeToProviderId; + + // Set as default for all common types not configured with a provider + + if (config.typeToProviderId.find(common::CryptoProviderType::DEFAULT) == config.typeToProviderId.end()) + { + // Prefer the HARDWARE provider (e.g., SoftHSM/PKCS#11) as DEFAULT when available, + // falling back to SOFTWARE (OpenSSL) and then the first registered provider. + common::ProviderId defaultId = common::kInvalidProviderId; + + // Search for HARDWARE or SOFTWARE provider + for (const auto& entry : m_providers) + { + if (entry.second.cryptoType == common::CryptoProviderType::HARDWARE) + { + defaultId = entry.second.numeric_id; + break; + } + } + if (defaultId == common::kInvalidProviderId) + { + for (const auto& entry : m_providers) + { + if (entry.second.cryptoType == common::CryptoProviderType::SOFTWARE) + { + defaultId = entry.second.numeric_id; + break; + } + } + } + if (defaultId == common::kInvalidProviderId) + { + defaultId = firstProviderId; + } + + config.SetDefaultProviderForType(common::CryptoProviderType::DEFAULT, defaultId); + } + if (config.typeToProviderId.find(common::CryptoProviderType::SOFTWARE) == config.typeToProviderId.end()) + { + config.SetDefaultProviderForType(common::CryptoProviderType::SOFTWARE, firstProviderId); + } + if (config.typeToProviderId.find(common::CryptoProviderType::HARDWARE) == config.typeToProviderId.end()) + { + config.SetDefaultProviderForType(common::CryptoProviderType::HARDWARE, firstProviderId); + } + if (config.typeToProviderId.find(common::CryptoProviderType::SPECIALIZED) == config.typeToProviderId.end()) + { + config.SetDefaultProviderForType(common::CryptoProviderType::SPECIALIZED, firstProviderId); + } + } + + return config; +} + +bool ProviderManager::CreateProviders() +{ + // Invoke each registered factory in order. + // Factories are wired externally (e.g. in daemon main()) via RegisterFactory(). + for (auto& factory : m_factories) + { + if (!factory->CreateAndRegister(*this)) + { + return false; + } + } + return true; +} + +void ProviderManager::RegisterFactory(std::unique_ptr factory) +{ + if (!factory) + { + throw std::runtime_error("RegisterFactory: factory must not be null"); + } + m_factories.emplace_back(std::move(factory)); +} + +bool ProviderManager::RegisterProvider(const common::ProviderName& providerName, + std::shared_ptr provider, + common::CryptoProviderType cryptoType) +{ + // Check if provider name already exists + if (m_providers.find(providerName) != m_providers.end()) + { + return false; + } + + if (!provider) + { + throw std::runtime_error("Cannot register null provider for: " + providerName); + } + + // Assign numeric ID: next index in m_provider_by_id + common::ProviderId numeric_id = static_cast(m_provider_by_id.size()); + + // Store the instance in the vector for O(1) numeric lookup + m_provider_by_id.push_back(provider); + + // Store the entry in the map for O(1) name lookup + m_providers.emplace(providerName, ProviderEntry(providerName, numeric_id, provider, cryptoType)); + + // Map the type to this numeric ID if not already mapped + if (m_typeToProviderId.find(cryptoType) == m_typeToProviderId.end()) + { + m_typeToProviderId[cryptoType] = numeric_id; + } + + return true; +} + +std::shared_ptr ProviderManager::GetProvider(common::ProviderId providerId) const +{ + if (providerId >= m_provider_by_id.size()) + { + return nullptr; + } + return m_provider_by_id[providerId]; +} + +std::shared_ptr ProviderManager::GetProvider(const common::ProviderName& providerName) const +{ + auto it = m_providers.find(providerName); + if (it != m_providers.end()) + { + return it->second.instance; + } + return nullptr; +} + +std::shared_ptr ProviderManager::GetProvider(common::CryptoProviderType cryptoType) const +{ + auto it = m_typeToProviderId.find(cryptoType); + if (it != m_typeToProviderId.end()) + { + return GetProvider(it->second); + } + return nullptr; +} + +bool ProviderManager::SetDefaultProviderForType(common::CryptoProviderType cryptoType, common::ProviderId providerId) +{ + // Verify the provider exists by numeric ID + if (providerId >= m_provider_by_id.size() || !m_provider_by_id[providerId]) + { + return false; + } + + // Update the mapping - same provider can be default for multiple types + m_typeToProviderId[cryptoType] = providerId; + return true; +} + +bool ProviderManager::InitializeAll() +{ + for (auto& entry : m_providers) + { + ProviderInitContext ctx{entry.second.numeric_id, entry.first}; + if (!entry.second.instance->Initialize(ctx)) + { + return false; + } + } + return true; +} + +void ProviderManager::Shutdown() +{ + for (auto& pair : m_providers) + { + if (pair.second.instance) + { + pair.second.instance->Shutdown(); + } + } +} + +std::optional ProviderManager::GetProviderType( + const common::ProviderName& provider_name) const +{ + const auto it = m_providers.find(provider_name); + if (it == m_providers.end()) + { + return std::nullopt; + } + return it->second.cryptoType; +} + +bool ProviderManager::IsProviderCompatibleWithType(const common::ProviderId provider_id, + common::CryptoProviderType requested_type) const +{ + if (requested_type == common::CryptoProviderType::DEFAULT) + { + return true; + } + // Look up provider type by iterating through entries to find matching numeric_id + for (const auto& entry : m_providers) + { + if (entry.second.numeric_id == provider_id) + { + return entry.second.cryptoType == requested_type; + } + } + return false; +} + +} // namespace provider +} // namespace daemon +} // namespace score::crypto diff --git a/score/crypto/daemon/src/daemon.cpp b/score/crypto/daemon/src/daemon.cpp new file mode 100644 index 0000000..2ca8065 --- /dev/null +++ b/score/crypto/daemon/src/daemon.cpp @@ -0,0 +1,130 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include +#include +#include +#include +#include +#include +#include + +#include "score/crypto/daemon/config/inc/config.hpp" +#include "score/crypto/daemon/control_plane/basic_handler_chain_factory.hpp" +#include "score/crypto/daemon/control_plane/i_control_server.h" +#include "score/crypto/daemon/data_manager/data_manager.hpp" +#include "score/crypto/daemon/key_management/key_management_module.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" +#include "score/crypto/daemon/provider/score_provider/score_provider_factory.hpp" +#include "score/crypto/ipc/grpc_adapter/grpc_control_server.h" +#include "score/crypto/ipc/ipc_config.h" + +namespace score::crypto::daemon +{ + +static std::atomic g_shutdown_requested{false}; + +void SignalHandler(int signal) +{ + g_shutdown_requested.store(true); +} + +void SetupSignalHandlers() +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SignalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + sigaction(SIGINT, &sa, nullptr); // Ctrl+C + sigaction(SIGTERM, &sa, nullptr); // kill +} + +} // namespace score::crypto::daemon + +int main(int argc, char** argv) +{ + // Create and configure the daemon + score::crypto::daemon::config::Config config; + + // Parse configuration from multiple sources (priority: file < env < cmdline) + if (!config.ParseConfig()) + { + std::cerr << "Warning: Could not parse config file (may not exist)" << std::endl; + } + + auto provider_manager = std::make_shared(config); + + // Wire provider factories — each factory encapsulates construction and registration + // of one or more providers. Factories are called in order during Initialize(). + + // Score provider factory (OpenSSL software provider) + config.GetScoreProviderConfig().PopulateDefaults(); + auto score_factory = std::make_unique(); + config.GetScoreProviderConfig().Configure(*score_factory); + provider_manager->RegisterFactory(std::move(score_factory)); + + // Populate production PKCS#11 default tokens (SoftHSM) unless the config + // file already supplied entries. + config.GetPkcs11Config().PopulateDefaults(); + + // Pkcs11Config visits the factory: converts Pkcs11TokenEntry entries to + // Pkcs11ProviderConfig and calls factory.SetTokenConfigs() internally. + auto pkcs11_factory = std::make_unique(); + config.GetPkcs11Config().Configure(*pkcs11_factory); + provider_manager->RegisterFactory(std::move(pkcs11_factory)); + + provider_manager->Initialize(); + + // Create data manager + auto data_manager = std::make_shared(); + + // Initialize key management subsystem + auto key_mgmt_module = score::crypto::daemon::key_management::KeyManagementModule::Create( + data_manager, provider_manager, config.GetKeyConfig()); + + // Set HandlerChainFactory to be used by IControlServer + auto handler_factory = std::make_unique( + data_manager, // Shared thread-safe data manager + provider_manager, // Shared thread-safe provider manager + config, // Config by reference (outlives factory) + key_mgmt_module ? key_mgmt_module->GetService() : nullptr // Key management service + ); + + std::unique_ptr server = + std::make_unique(std::move(handler_factory)); + + score::crypto::daemon::SetupSignalHandlers(); + + // Start server in background thread + std::thread server_thread([&server]() { + server->Start(score::crypto::ipc::kControlSocket); + server->WaitForTermination(); + }); + + // Main daemon loop - wait for shutdown signal + while (!score::crypto::daemon::g_shutdown_requested.load()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + // Shutdown sequence + std::cout << "Termination requested, shutting down daemon..." << std::endl; + server->Stop(); + server_thread.join(); + + std::cout << "Daemon shutdown complete" << std::endl; + return 0; +} diff --git a/score/crypto/ipc/BUILD b/score/crypto/ipc/BUILD new file mode 100644 index 0000000..6696faf --- /dev/null +++ b/score/crypto/ipc/BUILD @@ -0,0 +1,20 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +cc_library( + name = "ipc_config", + hdrs = ["ipc_config.h"], + linkstatic = True, # Internal library should only be statically linked + visibility = ["//:__subpackages__"], +) diff --git a/score/crypto/ipc/grpc_adapter/BUILD b/score/crypto/ipc/grpc_adapter/BUILD new file mode 100644 index 0000000..da18578 --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/BUILD @@ -0,0 +1,75 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:defs.bzl", "cc_shared_library") + +genrule( + name = "generated_grpc_control_server", + srcs = ["control.fbs"], + outs = [ + "control_generated.h", + "control.grpc.fb.h", + "control.grpc.fb.cc", + ], + cmd = "$(location @flatbuffers//:flatc) --cpp --grpc -o $(@D) $(SRCS)", + tools = ["@flatbuffers//:flatc"], +) + +cc_library( + name = "grpc_control_server", + srcs = [ + "grpc_control_handler.h", + "src/grpc_control_handler.cpp", + "src/grpc_control_server.cpp", + ":generated_grpc_control_server", + ], + hdrs = [ + "grpc_control_server.h", + ], + implementation_deps = [ + "@grpc//:grpc++_unsecure", + ], + linkstatic = True, # Internal library should only be statically linked + visibility = ["//:__subpackages__"], + deps = [ + "//score/crypto/daemon/control_plane", + "@flatbuffers", + ], +) + +cc_library( + name = "grpc_control_client", + srcs = [ + "src/grpc_control_client.cpp", + "src/grpc_id_helpers.cpp", + ":generated_grpc_control_server", + ], + hdrs = [ + "grpc_control_client.h", # No factory yet + "src/grpc_id_helpers.h", + ], + implementation_deps = [ + "@grpc//:grpc++_unsecure", + ], + linkstatic = True, # Internal library should only be statically linked + visibility = [ + "//score/crypto/api:__subpackages__", + "//score/crypto/daemon/IPC:__pkg__", + "//tests:__subpackages__", + ], + deps = [ + "//score/crypto/api/control_plane", + "//score/crypto/daemon/control_plane:request_handler_hdr", + "@flatbuffers", + ], +) diff --git a/score/crypto/ipc/grpc_adapter/control.fbs b/score/crypto/ipc/grpc_adapter/control.fbs new file mode 100644 index 0000000..3ef94bf --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/control.fbs @@ -0,0 +1,138 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +// Control structure for crypto daemon IPC +// This shall mainly transfer control information +// (Bigger) data elements can be transferred, but should rather be transfered via the data plane +// which makes use of zero-copy (e.g. via shared memory) + +file_identifier "CCTL"; + +namespace score.crypto.ipc.control; + +// Basic Operation Structures + +union OperationParameter { + NoParam, + + ValueBool, + ValueUint8, + ValueUint16, + ValueUint32, + ValueUint64, + + DataBufferInBand, + DataBufferDataPlane, + + String, +} + +table SingleOperationRequest { + operation_id: OperationIdentifier; + parameter: [OperationParameter]; +} + +table SingleOperationResponse { + operation_id: OperationIdentifier; + result: OperationResult; + parameter: [OperationParameter]; +} + +table OperationIdentifier { + operation_actor: uint16; + operation_action: uint16; +} + +table OperationResult { + val: uint32; +} + +// Operation Parameter + +table NoParam { +} + +table ValueBool { + val: bool; +} + +table ValueUint8 { + val: uint8; +} + +table ValueUint16 { + val: uint16; +} + +table ValueUint32 { + val: uint32; +} + +table ValueUint64 { + val: uint64; +} + +table String { + val: string; +} + +table DataBufferInBand { + val: [uint8]; +} + +table DataBufferDataPlane { + // TODO: Whatever is needed here +} + +// Operation batching to allow transfer of multiple operations in a single IPC interaction + +table OperationRequestBatch { + operations: [SingleOperationRequest]; +} + +table OperationResponseBatch { + operations: [SingleOperationResponse]; +} + +// IPC messages +table ControlRequest { + // Identifier to match a Response to a Request + // Obsolete in sync GRPC + request_id: uint64; + + // Identifier of the client + // Goal is an authentic identifier of the caller + // Currently implemented as (PID | UID) + // with INSECURE retrieval and transfer in the GRPC based implementation + client_id: uint64; + + // Identifier of state-full DataNodes in the DataNodeManager + // Binds API side elements to DataNodes + // The concrete type of DataNode depends on the context of use + // e.g. session or hashContext + data_node_id: uint64; + + // OperationRequest + operation_batch: OperationRequestBatch; +} + +table ControlResponse { + // Identifier to match a Response to a Request + // Obsolete in sync GRPC + request_id: uint64; + + operation_batch: OperationResponseBatch; +} + +rpc_service ControlService { + Execute(ControlRequest): ControlResponse; +} diff --git a/score/crypto/ipc/grpc_adapter/grpc_control_client.h b/score/crypto/ipc/grpc_adapter/grpc_control_client.h new file mode 100644 index 0000000..0495517 --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/grpc_control_client.h @@ -0,0 +1,58 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +// Renamed from grpc_client.h to grpc_control_client.h +#ifndef GRPC_CONTROL_CLIENT_H +#define GRPC_CONTROL_CLIENT_H + +#include "score/crypto/api/control_plane/i_connection.hpp" + +#include +#include + +namespace score::crypto::ipc +{ + +/// gRPC implementation of IConnection +/// Handles transport-specific details (FlatBuffers serialization, gRPC calls) +/// and converts between business logic types and wire protocol +class GrpcControlClient : public api::control_plane::IConnection +{ + public: + /// Create a gRPC client connected to the specified socket pathpconnection_impl.hpp:30: + /// @param socket_path Path to Unix domain socket + explicit GrpcControlClient(std::string_view socket_path); + ~GrpcControlClient() override; + + /// Disable copy and move + GrpcControlClient(const GrpcControlClient&) = delete; + GrpcControlClient& operator=(const GrpcControlClient&) = delete; + GrpcControlClient(GrpcControlClient&&) = delete; + GrpcControlClient& operator=(GrpcControlClient&&) = delete; + + /// Send a control request and receive response (IConnection implementation) + /// @param request Business logic request + /// @return Business logic response + Expected SendRequest( + const daemon::control_plane::protocol::ControlRequest& request) override; + + /// Get the connection node ID (IConnection implementation) + daemon::control_plane::protocol::DataNodeId GetConnectionNodeId() const override; + + private: + struct Impl; + std::unique_ptr _impl; +}; + +} // namespace score::crypto::ipc + +#endif // GRPC_CONTROL_CLIENT_H diff --git a/score/crypto/ipc/grpc_adapter/grpc_control_handler.h b/score/crypto/ipc/grpc_adapter/grpc_control_handler.h new file mode 100644 index 0000000..6b7e9a9 --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/grpc_control_handler.h @@ -0,0 +1,64 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +// Renamed from grpc_adapter.h to grpc_handler.h +// Renamed from grpc_handler.h to grpc_control_handler.h +#ifndef GRPC_CONTROL_HANDLER_H +#define GRPC_CONTROL_HANDLER_H + +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/daemon/control_plane/i_handler_chain_factory.hpp" +#include "score/crypto/ipc/grpc_adapter/control.grpc.fb.h" +#include +#include + +namespace score::crypto::ipc +{ + +class GrpcControlServiceAdapter final : public score::crypto::ipc::control::ControlService::Service +{ + public: + explicit GrpcControlServiceAdapter( + std::unique_ptr factory); + + grpc::Status Execute(grpc::ServerContext* context, + const flatbuffers::grpc::Message* request, + flatbuffers::grpc::Message* response) override; + + private: + std::unique_ptr _factory; + + // Thread-local handler instance (one per gRPC worker thread) + static thread_local std::unique_ptr _thread_handler; + + // Convert FlatBuffer message to business logic struct + daemon::control_plane::protocol::ControlRequest ConvertRequest( + const score::crypto::ipc::control::ControlRequest* fb_req); + + // Extract request parameters from FlatBuffer format + static daemon::common::RequestParameters ExtractRequestParameters( + const flatbuffers::Vector* fb_param_types, + const flatbuffers::Vector>* fb_params); + + // Convert business logic struct to FlatBuffer message + void ConvertResponse(const daemon::control_plane::protocol::ControlResponse& bl_resp, + flatbuffers::grpc::Message* fb_resp); + + // Build response parameters into FlatBuffer format + static std::pair, std::vector<::flatbuffers::Offset>> BuildResponseParameters( + const daemon::common::ResponseParameters& bl_params, + flatbuffers::grpc::MessageBuilder& builder); +}; + +} // namespace score::crypto::ipc + +#endif // GRPC_CONTROL_HANDLER_H diff --git a/score/crypto/ipc/grpc_adapter/grpc_control_server.h b/score/crypto/ipc/grpc_adapter/grpc_control_server.h new file mode 100644 index 0000000..49ad320 --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/grpc_control_server.h @@ -0,0 +1,51 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef GRPC_CONTROL_SERVER_H +#define GRPC_CONTROL_SERVER_H + +#include +#include + +#include "score/crypto/daemon/control_plane/i_control_server.h" +#include "score/crypto/daemon/control_plane/i_handler_chain_factory.hpp" + +namespace score::crypto::ipc +{ + +// gRPC implementation of IControlServer +// Manages server lifecycle and socket cleanup +class GrpcControlServer final : public daemon::control_plane::IControlServer +{ + public: + explicit GrpcControlServer(std::unique_ptr factory); + ~GrpcControlServer() override; + + // Disable copy and move + GrpcControlServer(const GrpcControlServer&) = delete; + GrpcControlServer& operator=(const GrpcControlServer&) = delete; + GrpcControlServer(GrpcControlServer&&) = delete; + GrpcControlServer& operator=(GrpcControlServer&&) = delete; + + // IControlServer implementation + void Start(std::string_view socket_path) override; + void Stop() override; + void WaitForTermination() override; + + private: + struct Impl; + std::unique_ptr _impl; +}; + +} // namespace score::crypto::ipc + +#endif // GRPC_CONTROL_SERVER_H diff --git a/score/crypto/ipc/grpc_adapter/src/grpc_control_client.cpp b/score/crypto/ipc/grpc_adapter/src/grpc_control_client.cpp new file mode 100644 index 0000000..6e9e645 --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/src/grpc_control_client.cpp @@ -0,0 +1,413 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/ipc/grpc_adapter/grpc_control_client.h" +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/ipc/grpc_adapter/control.grpc.fb.h" +#include "score/crypto/ipc/grpc_adapter/control_generated.h" +#include "score/crypto/ipc/grpc_adapter/src/grpc_id_helpers.h" + +#include "flatbuffers/grpc.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace score::crypto::ipc +{ + +// PIMPL implementation - hides grpc types from public header +struct GrpcControlClient::Impl +{ + std::unique_ptr + stub; // NOLINT(misc-non-private-member-variables-in-classes) + + explicit Impl(std::string_view socket_path) + { + auto channel = grpc::CreateChannel("unix:" + std::string(socket_path), grpc::InsecureChannelCredentials()); + stub = score::crypto::ipc::control::ControlService::NewStub(channel); + } + + flatbuffers::grpc::Message ConvertRequest( + const daemon::control_plane::protocol::ControlRequest& bl_req, + flatbuffers::grpc::MessageBuilder& mb); + + daemon::control_plane::protocol::ControlResponse ConvertResponse( + const score::crypto::ipc::control::ControlResponse* fb_resp); + + static daemon::common::ResponseParameters ExtractResponseParameters( + const flatbuffers::Vector* fb_param_types, + const flatbuffers::Vector>* fb_params); + + static std::pair, std::vector<::flatbuffers::Offset>> BuildRequestParameters( + const daemon::common::RequestParameters& bl_params, + flatbuffers::grpc::MessageBuilder& mb); +}; + +GrpcControlClient::GrpcControlClient(std::string_view socket_path) : _impl(std::make_unique(socket_path)) {} + +GrpcControlClient::~GrpcControlClient() = default; + +Expected +GrpcControlClient::SendRequest(const daemon::control_plane::protocol::ControlRequest& request) +{ + // TODO: Avoid copy + auto enhanced_request = request; + enhanced_request.request_id = RequestId::getRequestId(); + enhanced_request.uid = InsecureClientId::getUid(); + enhanced_request.pid = InsecureClientId::getPid(); + + // Convert business logic request → FlatBuffer + flatbuffers::grpc::MessageBuilder mb; + auto fb_request = _impl->ConvertRequest(enhanced_request, mb); + + // Send gRPC call + grpc::ClientContext context; + flatbuffers::grpc::Message fb_response; + + const grpc::Status status = _impl->stub->Execute(&context, fb_request, &fb_response); + + if (!status.ok()) + { + std::cerr << "[GrpcControlClient] [Thread " << std::this_thread::get_id() << "] " + << "gRPC call failed for RequestID: " << request.request_id << " | Error: " << status.error_message() + << "\n"; + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInternalError); + } + // Convert FlatBuffer response → business logic + auto response = _impl->ConvertResponse(fb_response.GetRoot()); + + if (enhanced_request.request_id != response.request_id) + { + std::cerr << " [MISMATCH!] RequestID mismatch - received: \n"; + return make_unexpected(score::mw::crypto::CryptoErrorCode::kInternalError); + } + + return response; +} + +daemon::control_plane::protocol::DataNodeId GrpcControlClient::GetConnectionNodeId() const +{ + // GrpcControlClient is a pure transport implementation managed by ConnectionImpl. + // The connection node ID is stored in ConnectionImpl, not here. + // Return 0 to indicate no ID is set at this level. + return 0; +} + +flatbuffers::grpc::Message GrpcControlClient::Impl::ConvertRequest( + const daemon::control_plane::protocol::ControlRequest& bl_req, + flatbuffers::grpc::MessageBuilder& mb) +{ + // Build operation request batch + std::vector<::flatbuffers::Offset> fb_single_requests; + + for (const auto& bl_single_req : bl_req.operation.operations) + { + // Build operation identifier + auto fb_op_id = score::crypto::ipc::control::CreateOperationIdentifier( + mb, bl_single_req.operationId.operationActor, bl_single_req.operationId.operationAction); + + // Build request parameters + auto [fb_param_types, fb_params] = BuildRequestParameters(bl_single_req.parameters, mb); + + // Create parameter vectors + auto fb_param_types_vec = mb.CreateVector(fb_param_types); + auto fb_params_vec = mb.CreateVector(fb_params); + + // Build single operation request + auto fb_single_req = + score::crypto::ipc::control::CreateSingleOperationRequest(mb, fb_op_id, fb_param_types_vec, fb_params_vec); + + fb_single_requests.push_back(fb_single_req); + } + + // Create operation request batch + auto fb_op_batch = + score::crypto::ipc::control::CreateOperationRequestBatch(mb, mb.CreateVector(fb_single_requests)); + + // Build ControlRequest + auto fb_request = score::crypto::ipc::control::CreateControlRequest( + mb, bl_req.request_id, bl_req.client_id, bl_req.data_node_id, fb_op_batch); + + // Finish the request and return as Message + mb.Finish(fb_request); + return mb.GetMessage(); +} + +daemon::control_plane::protocol::ControlResponse GrpcControlClient::Impl::ConvertResponse( + const score::crypto::ipc::control::ControlResponse* fb_resp) +{ + // Create a new ControlResponse with extracted data from FlatBuffer + daemon::control_plane::protocol::ControlResponse bl_resp{}; + + // Extract scalar fields + bl_resp.request_id = fb_resp->request_id(); + + // Extract operation batch + const auto* fb_op_batch = fb_resp->operation_batch(); + if (fb_op_batch && fb_op_batch->operations()) + { + // Iterate over each SingleOperationResponse in the batch + for (const auto* fb_single_resp : *fb_op_batch->operations()) + { + if (!fb_single_resp) + continue; + + // Create a SingleOperationResponse in business logic + daemon::control_plane::protocol::SingleOperationResponse bl_single_resp{}; + + // Extract operation identifier + const auto* fb_op_id = fb_single_resp->operation_id(); + if (fb_op_id) + { + bl_single_resp.operationId.operationActor = fb_op_id->operation_actor(); + bl_single_resp.operationId.operationAction = fb_op_id->operation_action(); + } + + // Extract operation result + const auto* fb_result = fb_single_resp->result(); + if (fb_result) + { + bl_single_resp.result = static_cast(fb_result->val()); + } + + // Extract response parameters + bl_single_resp.parameters = + ExtractResponseParameters(fb_single_resp->parameter_type(), fb_single_resp->parameter()); + + // Add the single operation to the batch + bl_resp.operation.operations.push_back(bl_single_resp); + } + } + + return bl_resp; +} + +daemon::common::ResponseParameters GrpcControlClient::Impl::ExtractResponseParameters( + const flatbuffers::Vector* fb_param_types, + const flatbuffers::Vector>* fb_params) +{ + daemon::common::ResponseParameters parameters; + + if (!fb_param_types || !fb_params || fb_param_types->size() != fb_params->size()) + { + return parameters; + } + + for (size_t i = 0; i < fb_param_types->size(); ++i) + { + auto param_type = fb_param_types->Get(i); + const auto* fb_param = fb_params->Get(i); + + if (!fb_param) + continue; + + switch (static_cast(param_type)) + { + case score::crypto::ipc::control::OperationParameter_NoParam: + { + parameters.emplace_back(daemon::control_plane::protocol::NoParam{}); + break; + } + case score::crypto::ipc::control::OperationParameter_ValueBool: + { + const auto* fb_val = + static_cast(static_cast(fb_param)); + if (fb_val) + { + parameters.emplace_back(bool{fb_val->val()}); + } + break; + } + case score::crypto::ipc::control::OperationParameter_ValueUint8: + { + const auto* fb_val = + static_cast(static_cast(fb_param)); + if (fb_val) + { + parameters.emplace_back(std::uint8_t{fb_val->val()}); + } + break; + } + case score::crypto::ipc::control::OperationParameter_ValueUint16: + { + const auto* fb_val = + static_cast(static_cast(fb_param)); + if (fb_val) + { + parameters.emplace_back(std::uint16_t{fb_val->val()}); + } + break; + } + case score::crypto::ipc::control::OperationParameter_ValueUint32: + { + const auto* fb_val = + static_cast(static_cast(fb_param)); + if (fb_val) + { + parameters.emplace_back(std::uint32_t{fb_val->val()}); + } + break; + } + case score::crypto::ipc::control::OperationParameter_ValueUint64: + { + const auto* fb_val = + static_cast(static_cast(fb_param)); + if (fb_val) + { + parameters.emplace_back(std::uint64_t{fb_val->val()}); + } + break; + } + case score::crypto::ipc::control::OperationParameter_String: + { + const auto* fb_string = + static_cast(static_cast(fb_param)); + if (fb_string && fb_string->val()) + { + parameters.emplace_back(daemon::control_plane::protocol::OwnedString(fb_string->val()->c_str())); + } + break; + } + case score::crypto::ipc::control::OperationParameter_DataBufferInBand: + { + const auto* fb_buffer = static_cast( + static_cast(fb_param)); + if (fb_buffer && fb_buffer->val()) + { + // Copy FlatBuffer byte vector into a std::vector (must own - FB goes out of scope) + const auto* fb_data = fb_buffer->val(); + daemon::control_plane::protocol::OwnedBuffer data_copy(fb_data->begin(), fb_data->end()); + parameters.emplace_back(std::move(data_copy)); + } + break; + } + case score::crypto::ipc::control::OperationParameter_DataBufferDataPlane: + case score::crypto::ipc::control::OperationParameter_NONE: + default: + { + // TODO: Not all types of the varaint are implemented. However all + // which are currently used are implemented. + // In case we end up here, we may need to implemented serialization / deserializazion + // for the concrete type. + + std::cerr << "[GrpcControlClient] ERROR - Unsupported parameter type: " << static_cast(param_type) + << "\n"; + break; + } + } + } + + return parameters; +} + +std::pair, std::vector<::flatbuffers::Offset>> +GrpcControlClient::Impl::BuildRequestParameters(const daemon::common::RequestParameters& bl_params, + flatbuffers::grpc::MessageBuilder& mb) +{ + std::vector fb_param_types; + std::vector<::flatbuffers::Offset> fb_params; + + for (const auto& bl_param : bl_params) + { + if (std::holds_alternative(bl_param)) + { + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_NoParam); + auto fb_no_param = score::crypto::ipc::control::CreateNoParam(mb); + fb_params.emplace_back(fb_no_param.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& val = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_ValueBool); + auto fb_val = score::crypto::ipc::control::CreateValueBool(mb, val); + fb_params.emplace_back(fb_val.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& str = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_String); + auto fb_str_offset = mb.CreateString(str); + auto fb_string = score::crypto::ipc::control::CreateString(mb, fb_str_offset); + fb_params.emplace_back(fb_string.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& buffer = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_DataBufferInBand); + auto fb_data = mb.CreateVector(buffer.data, buffer.size); + auto fb_buffer = score::crypto::ipc::control::CreateDataBufferInBand(mb, fb_data); + fb_params.emplace_back(fb_buffer.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& buffer = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_DataBufferInBand); + auto fb_data = mb.CreateVector(buffer.data, buffer.size); + auto fb_buffer = score::crypto::ipc::control::CreateDataBufferInBand(mb, fb_data); + fb_params.emplace_back(fb_buffer.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& val = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_ValueUint8); + auto fb_val = score::crypto::ipc::control::CreateValueUint8(mb, val); + fb_params.emplace_back(fb_val.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& val = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_ValueUint16); + auto fb_val = score::crypto::ipc::control::CreateValueUint16(mb, val); + fb_params.emplace_back(fb_val.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& val = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_ValueUint32); + auto fb_val = score::crypto::ipc::control::CreateValueUint32(mb, val); + fb_params.emplace_back(fb_val.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& val = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_ValueUint64); + auto fb_val = score::crypto::ipc::control::CreateValueUint64(mb, val); + fb_params.emplace_back(fb_val.o); + } + else + { + // TODO: Not all types of the varaint are implemented. However all + // which are currently used are implemented. + // In case we end up here, we may need to implemented serialization / deserializazion + // for the concrete type. + + std::cerr << "[GrpcControlClient] ERROR - Unsupported parameter type in request\n"; + // Skip unsupported parameter types + continue; + } + } + + return {fb_param_types, fb_params}; +} + +} // namespace score::crypto::ipc diff --git a/score/crypto/ipc/grpc_adapter/src/grpc_control_handler.cpp b/score/crypto/ipc/grpc_adapter/src/grpc_control_handler.cpp new file mode 100644 index 0000000..ece1a43 --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/src/grpc_control_handler.cpp @@ -0,0 +1,391 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/ipc/grpc_adapter/grpc_control_handler.h" +#include "score/crypto/daemon/common/types.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/daemon/control_plane/i_handler_chain_factory.hpp" +#include "score/crypto/daemon/control_plane/i_request_handler.hpp" +#include "score/crypto/ipc/grpc_adapter/control.grpc.fb.h" +#include "score/crypto/ipc/grpc_adapter/control_generated.h" + +#include "flatbuffers/grpc.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace score::crypto::ipc +{ + +// Thread-local handler storage (one per gRPC worker thread) +thread_local std::unique_ptr + GrpcControlServiceAdapter::_thread_handler = nullptr; + +GrpcControlServiceAdapter::GrpcControlServiceAdapter( + std::unique_ptr factory) + : _factory(std::move(factory)) +{ +} + +grpc::Status GrpcControlServiceAdapter::Execute( + grpc::ServerContext* /*context*/, + const flatbuffers::grpc::Message* request, + flatbuffers::grpc::Message* response) +{ + + const auto* fb_req = request->GetRoot(); + if (!fb_req) + { + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Invalid request"); + } + + // Convert FlatBuffer → Business Logic + auto bl_req = ConvertRequest(fb_req); + + std::cout << "[GrpcControlServiceAdapter] [Server Thread " << std::this_thread::get_id() << "] " + << "Processing request with RequestID: " << bl_req.request_id << " | User Id: " << bl_req.client_id + << " | DataNodeId: " << bl_req.data_node_id << "\n"; + + // Lazy initialization: create handler for this thread on first request + if (!_thread_handler) + { + std::cout << "[GrpcControlServiceAdapter] [Server Thread " << std::this_thread::get_id() << "] " + << "Initializing thread-local request handler\n"; + _thread_handler = _factory->CreateRequestHandler(); + } + + // Execute business logic (transport-agnostic) + auto bl_resp = _thread_handler->processRequest(bl_req); + + std::cout << "[GrpcControlServiceAdapter] [Server Thread " << std::this_thread::get_id() << "] " + << "Processed request, returning response with RequestID: " << bl_resp.request_id + << " (Request had: " << bl_req.request_id << ")\n"; + + // Convert Business Logic → FlatBuffer + ConvertResponse(bl_resp, response); + + return grpc::Status::OK; +} + +daemon::control_plane::protocol::ControlRequest GrpcControlServiceAdapter::ConvertRequest( + const score::crypto::ipc::control::ControlRequest* fb_req) +{ + // Create a new ControlRequest with extracted data from FlatBuffer + daemon::control_plane::protocol::ControlRequest bl_req{}; + + // Extract scalar fields from FlatBuffer + bl_req.request_id = fb_req->request_id(); + + // Extract client_id (union field) - assign as the union variant + bl_req.client_id = fb_req->client_id(); + + // Extract data_node_id (union field) - assign as the union variant + bl_req.data_node_id = fb_req->data_node_id(); + + // Extract operation batch + const auto* fb_op_batch = fb_req->operation_batch(); + if (fb_op_batch && fb_op_batch->operations()) + { + // Iterate over each SingleOperationRequest in the batch + for (const auto* fb_single_op : *fb_op_batch->operations()) + { + if (!fb_single_op) + continue; + + // Create a SingleOperationRequest in business logic + daemon::control_plane::protocol::SingleOperationRequest bl_single_op{}; + + // Extract operation identifier + const auto* fb_op_id = fb_single_op->operation_id(); + if (fb_op_id) + { + bl_single_op.operationId.operationActor = fb_op_id->operation_actor(); + bl_single_op.operationId.operationAction = fb_op_id->operation_action(); + } + + // Extract request parameters + bl_single_op.parameters = + ExtractRequestParameters(fb_single_op->parameter_type(), fb_single_op->parameter()); + + // Add the single operation to the batch + bl_req.operation.operations.push_back(bl_single_op); + } + } + + return bl_req; +} + +void GrpcControlServiceAdapter::ConvertResponse( + const daemon::control_plane::protocol::ControlResponse& bl_resp, + flatbuffers::grpc::Message* fb_resp) +{ + // Create a FlatBuffer builder for constructing the response + flatbuffers::grpc::MessageBuilder builder; + + // Build operation response batch + std::vector<::flatbuffers::Offset> fb_single_responses; + + for (const auto& bl_single_resp : bl_resp.operation.operations) + { + // Build operation identifier + auto fb_op_id = score::crypto::ipc::control::CreateOperationIdentifier( + builder, bl_single_resp.operationId.operationActor, bl_single_resp.operationId.operationAction); + + // Build operation result + auto fb_result = + score::crypto::ipc::control::CreateOperationResult(builder, static_cast(bl_single_resp.result)); + + // Build response parameters + auto [fb_param_types, fb_params] = BuildResponseParameters(bl_single_resp.parameters, builder); + + // Create parameter vectors + auto fb_param_types_vec = builder.CreateVector(fb_param_types); + auto fb_params_vec = builder.CreateVector(fb_params); + + // Build single operation response + auto fb_single_resp = score::crypto::ipc::control::CreateSingleOperationResponse( + builder, fb_op_id, fb_result, fb_param_types_vec, fb_params_vec); + + fb_single_responses.push_back(fb_single_resp); + } + + // Create operation response batch + auto fb_op_batch = + score::crypto::ipc::control::CreateOperationResponseBatch(builder, builder.CreateVector(fb_single_responses)); + + // Build ControlResponse + auto fb_response = score::crypto::ipc::control::CreateControlResponse(builder, bl_resp.request_id, fb_op_batch); + + // Finish the builder and extract the Message + builder.Finish(fb_response); + *fb_resp = builder.GetMessage(); +} + +std::pair, std::vector<::flatbuffers::Offset>> +GrpcControlServiceAdapter::BuildResponseParameters(const daemon::common::ResponseParameters& bl_params, + flatbuffers::grpc::MessageBuilder& builder) +{ + std::vector fb_param_types; + std::vector<::flatbuffers::Offset> fb_params; + + for (const auto& bl_param : bl_params) + { + if (std::holds_alternative(bl_param)) + { + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_NoParam); + auto fb_no_param = score::crypto::ipc::control::CreateNoParam(builder); + fb_params.emplace_back(fb_no_param.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& val = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_ValueBool); + auto fb_val = score::crypto::ipc::control::CreateValueBool(builder, val); + fb_params.emplace_back(fb_val.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& val = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_ValueUint8); + auto fb_val = score::crypto::ipc::control::CreateValueUint8(builder, val); + fb_params.emplace_back(fb_val.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& val = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_ValueUint16); + auto fb_val = score::crypto::ipc::control::CreateValueUint16(builder, val); + fb_params.emplace_back(fb_val.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& val = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_ValueUint32); + auto fb_val = score::crypto::ipc::control::CreateValueUint32(builder, val); + fb_params.emplace_back(fb_val.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& val = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_ValueUint64); + auto fb_val = score::crypto::ipc::control::CreateValueUint64(builder, val); + fb_params.emplace_back(fb_val.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& str = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_String); + auto fb_str_offset = builder.CreateString(str.data(), str.size()); + auto fb_string = score::crypto::ipc::control::CreateString(builder, fb_str_offset); + fb_params.emplace_back(fb_string.o); + } + else if (std::holds_alternative(bl_param)) + { + const auto& buffer = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_DataBufferInBand); + // Create FlatBuffer vector directly from buffer data without intermediate copy + auto fb_data = builder.CreateVector(buffer.data(), buffer.size()); + auto fb_buffer = score::crypto::ipc::control::CreateDataBufferInBand(builder, fb_data); + fb_params.emplace_back(fb_buffer.o); + } + // Convert non-owning buffers to owning ones for the transfer via the IPC + else if (std::holds_alternative(bl_param)) + { + const auto& buffer = std::get(bl_param); + fb_param_types.emplace_back(score::crypto::ipc::control::OperationParameter_DataBufferInBand); + // Create FlatBuffer vector directly from buffer data without intermediate copy + auto fb_data = builder.CreateVector(buffer.data, buffer.size); + auto fb_buffer = score::crypto::ipc::control::CreateDataBufferInBand(builder, fb_data); + fb_params.emplace_back(fb_buffer.o); + } + else + { + // TODO: Not all types of the varaint are implemented. However all + // which are currently used are implemented. + // In case we end up here, we may need to implemented serialization / deserializazion + // for the concrete type. + + std::cerr << "[GrpcControlServiceAdapter] ERROR - Unsupported parameter type in response\n"; + // Skip unsupported parameter types + continue; + } + } + + return {fb_param_types, fb_params}; +} + +daemon::common::RequestParameters GrpcControlServiceAdapter::ExtractRequestParameters( + const flatbuffers::Vector* fb_param_types, + const flatbuffers::Vector>* fb_params) +{ + daemon::common::RequestParameters parameters; + + if (!fb_param_types || !fb_params || fb_param_types->size() != fb_params->size()) + { + return parameters; + } + + for (size_t i = 0; i < fb_param_types->size(); ++i) + { + auto param_type = fb_param_types->Get(i); + const auto* fb_param = fb_params->Get(i); + + if (!fb_param) + continue; + + switch (static_cast(param_type)) + { + case score::crypto::ipc::control::OperationParameter_NoParam: + { + parameters.emplace_back(daemon::control_plane::protocol::NoParam{}); + break; + } + case score::crypto::ipc::control::OperationParameter_ValueBool: + { + const auto* fb_val = + static_cast(static_cast(fb_param)); + if (fb_val) + { + parameters.emplace_back(bool{fb_val->val()}); + } + break; + } + case score::crypto::ipc::control::OperationParameter_ValueUint8: + { + const auto* fb_val = + static_cast(static_cast(fb_param)); + if (fb_val) + { + parameters.emplace_back(std::uint8_t{fb_val->val()}); + } + break; + } + case score::crypto::ipc::control::OperationParameter_ValueUint16: + { + const auto* fb_val = + static_cast(static_cast(fb_param)); + if (fb_val) + { + parameters.emplace_back(std::uint16_t{fb_val->val()}); + } + break; + } + case score::crypto::ipc::control::OperationParameter_ValueUint32: + { + const auto* fb_val = + static_cast(static_cast(fb_param)); + if (fb_val) + { + parameters.emplace_back(std::uint32_t{fb_val->val()}); + } + break; + } + case score::crypto::ipc::control::OperationParameter_ValueUint64: + { + const auto* fb_val = + static_cast(static_cast(fb_param)); + if (fb_val) + { + parameters.emplace_back(std::uint64_t{fb_val->val()}); + } + break; + } + case score::crypto::ipc::control::OperationParameter_String: + { + const auto* fb_string = + static_cast(static_cast(fb_param)); + if (fb_string && fb_string->val()) + { + parameters.emplace_back(std::string_view{fb_string->val()->c_str(), fb_string->val()->size()}); + } + break; + } + case score::crypto::ipc::control::OperationParameter_DataBufferInBand: + { + const auto* fb_buffer = static_cast( + static_cast(fb_param)); + if (fb_buffer && fb_buffer->val()) + { + // Zero-copy: borrow from FlatBuffer memory (alive for RPC duration) + const auto* fb_data = fb_buffer->val(); + parameters.emplace_back(daemon::common::VirtualMemoryBufferConst{fb_data->data(), fb_data->size()}); + } + break; + } + case score::crypto::ipc::control::OperationParameter_DataBufferDataPlane: + case score::crypto::ipc::control::OperationParameter_NONE: + default: + { + // TODO: Not all types of the varaint are implemented. However all + // which are currently used are implemented. + // In case we end up here, we may need to implemented serialization / deserializazion + // for the concrete type. + + std::cerr << "[GrpcControlServiceAdapter] ERROR - Unsupported parameter type: " + << static_cast(param_type) << "\n"; + break; + } + } + } + + return parameters; +} + +} // namespace score::crypto::ipc diff --git a/score/crypto/ipc/grpc_adapter/src/grpc_control_server.cpp b/score/crypto/ipc/grpc_adapter/src/grpc_control_server.cpp new file mode 100644 index 0000000..f87b9dd --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/src/grpc_control_server.cpp @@ -0,0 +1,106 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/ipc/grpc_adapter/grpc_control_server.h" +#include "score/crypto/ipc/grpc_adapter/grpc_control_handler.h" +#include +#include +#include +#include + +namespace score::crypto::ipc +{ + +struct GrpcControlServer::Impl +{ + std::unique_ptr service; + std::unique_ptr server; + std::string socket_path; + + explicit Impl(std::unique_ptr factory) + : service(std::make_unique(std::move(factory))) + { + } +}; + +GrpcControlServer::GrpcControlServer(std::unique_ptr factory) + : _impl(std::make_unique(std::move(factory))) +{ +} + +GrpcControlServer::~GrpcControlServer() +{ + Stop(); +} + +void GrpcControlServer::Start(std::string_view socket_path) +{ + _impl->socket_path = std::string(socket_path); + // Clean up stale socket file if it exists + unlink(_impl->socket_path.data()); + + grpc::ServerBuilder builder; + // Add "unix:" prefix for gRPC (transport-specific detail) + builder.AddListeningPort("unix:" + _impl->socket_path, grpc::InsecureServerCredentials()); + builder.RegisterService(_impl->service.get()); + // to limit the number of threads used by gRPC for time being + // TODO + builder.SetSyncServerOption(grpc::ServerBuilder::SyncServerOption::MIN_POLLERS, 1); + builder.SetSyncServerOption(grpc::ServerBuilder::SyncServerOption::MAX_POLLERS, 1); + + _impl->server = builder.BuildAndStart(); + if (!_impl->server) + { + throw std::runtime_error("Failed to start gRPC server on unix:" + std::string(socket_path)); + } + + std::cout << "[GrpcControlServer] !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"; + std::cout << "[GrpcControlServer] !!! WARNING: Using insecure mechanism for uid and pid !!!\n"; + std::cout << "[GrpcControlServer] !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"; + std::cout << "[GrpcControlServer] Listening on unix:" << _impl->socket_path << std::endl; +} + +void GrpcControlServer::Stop() +{ + if (_impl->server) + { + _impl->server->Shutdown(); + _impl->server.reset(); + + // Cleanup socket file + if (!_impl->socket_path.empty()) + { + if (unlink(_impl->socket_path.c_str()) == 0) + { + std::cout << "[GrpcControlServer] Cleaned up socket file: " << _impl->socket_path << std::endl; + } + else if (errno != ENOENT) + { + std::cerr << "[GrpcControlServer] Warning: Failed to remove socket file " << _impl->socket_path << ": " + << strerror(errno) << std::endl; + } + _impl->socket_path.clear(); + } + + std::cout << "[GrpcControlServer] gRPC Control Server shutdown complete" << std::endl; + } +} + +void GrpcControlServer::WaitForTermination() +{ + if (_impl->server) + { + _impl->server->Wait(); + } +} + +} // namespace score::crypto::ipc diff --git a/score/crypto/ipc/grpc_adapter/src/grpc_id_helpers.cpp b/score/crypto/ipc/grpc_adapter/src/grpc_id_helpers.cpp new file mode 100644 index 0000000..8442a53 --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/src/grpc_id_helpers.cpp @@ -0,0 +1,48 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include +#include +#include +#include +#include + +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/ipc/grpc_adapter/src/grpc_id_helpers.h" + +namespace score::crypto::ipc +{ + +std::atomic_uint32_t RequestId::counter = 0; + +std::uint64_t RequestId::getRequestId() +{ + static_assert(sizeof(pid_t) == EXPECTED_PID_TYPE_SIZE, "Error: The size of pid_t is not the expected size."); + + uint64_t request_id = getpid(); + request_id <<= EXPECTED_PID_TYPE_SIZE * BITS_PER_BYTE; + request_id |= counter.fetch_add(1); + + return request_id; +} + +decltype(daemon::control_plane::protocol::ControlRequest::uid) InsecureClientId::getUid() +{ + return getuid(); +} + +decltype(daemon::control_plane::protocol::ControlRequest::pid) InsecureClientId::getPid() +{ + return getpid(); +} + +} // namespace score::crypto::ipc diff --git a/score/crypto/ipc/grpc_adapter/src/grpc_id_helpers.h b/score/crypto/ipc/grpc_adapter/src/grpc_id_helpers.h new file mode 100644 index 0000000..9ba6a6c --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/src/grpc_id_helpers.h @@ -0,0 +1,47 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef GRPC_ID_HELPERS_H +#define GRPC_ID_HELPERS_H + +#include +#include +#include + +#include "score/crypto/daemon/control_plane/control_protocol.h" + +namespace score::crypto::ipc +{ + +constexpr size_t EXPECTED_PID_TYPE_SIZE = sizeof(std::uint32_t); +constexpr size_t EXPECTED_UID_TYPE_SIZE = sizeof(std::uint32_t); +constexpr size_t BITS_PER_BYTE = 8; + +class RequestId +{ + public: + static std::uint64_t getRequestId(); + + private: + static std::atomic_uint32_t counter; +}; + +class InsecureClientId +{ + public: + static decltype(daemon::control_plane::protocol::ControlRequest::uid) getUid(); + static decltype(daemon::control_plane::protocol::ControlRequest::pid) getPid(); +}; + +} // namespace score::crypto::ipc + +#endif // GRPC_ID_HELPERS_H diff --git a/score/crypto/ipc/ipc_config.h b/score/crypto/ipc/ipc_config.h new file mode 100644 index 0000000..7c622e9 --- /dev/null +++ b/score/crypto/ipc/ipc_config.h @@ -0,0 +1,26 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef CRYPTO_IPC_CONFIG_H +#define CRYPTO_IPC_CONFIG_H + +#include + +namespace score::crypto::ipc +{ + +// Default Unix domain socket path for control communication +constexpr std::string_view kControlSocket = "/tmp/crypto_daemon.sock"; + +} // namespace score::crypto::ipc + +#endif // CRYPTO_IPC_CONFIG_H diff --git a/score/mw/crypto/api/BUILD b/score/mw/crypto/api/BUILD new file mode 100644 index 0000000..af0df50 --- /dev/null +++ b/score/mw/crypto/api/BUILD @@ -0,0 +1,44 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "crypto_stack", + srcs = [ + "src/crypto_context_impl.cpp", + "src/crypto_context_impl.hpp", + "src/crypto_stack_factory.cpp", + "src/crypto_stack_impl.cpp", + "src/crypto_stack_impl.hpp", + "src/provider_type_converter.hpp", + ], + hdrs = [ + "crypto_stack_factory.hpp", + "i_crypto_context.hpp", + "i_crypto_stack.hpp", + ], + includes = ["."], + visibility = ["//visibility:public"], + deps = [ + "//score/crypto/api:operations", + "//score/crypto/api/control_plane:control_plane_impl", + "//score/mw/crypto/api/common:crypto_common", + "//score/mw/crypto/api/config:context_configs", + "//score/mw/crypto/api/contexts:context_bases", + "//score/mw/crypto/api/contexts:crypto_contexts", + "//score/mw/crypto/api/contexts:crypto_contexts_impl", + "//score/mw/crypto/api/objects:crypto_objects", + "@score_baselibs//score/language/futurecpp", + "@score_baselibs//score/result", + ], +) diff --git a/score/mw/crypto/api/common/BUILD b/score/mw/crypto/api/common/BUILD new file mode 100644 index 0000000..969c821 --- /dev/null +++ b/score/mw/crypto/api/common/BUILD @@ -0,0 +1,52 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "crypto_common", + srcs = [ + "src/crypto_resource_guard.cpp", + "src/crypto_resource_guard_factory.hpp", # implementation-private; not exposed via hdrs + "src/error_domain.cpp", + "src/i_release_callback.hpp", # implementation-private; not exposed via hdrs + ], + hdrs = [ + "crypto_resource_guard.hpp", + "error_domain.hpp", + "fixed_capacity_string.hpp", + "i_memory_allocator.hpp", + "i_memory_region.hpp", + "types.hpp", + ], + includes = ["."], + visibility = ["//visibility:public"], + deps = [ + "@score_baselibs//score/language/futurecpp", + "@score_baselibs//score/result", + ], +) + +# Internal target for IPC-layer code that implements IReleaseCallback. +# Application code must not depend on this target. +cc_library( + name = "release_callback_iface", + hdrs = ["src/i_release_callback.hpp"], + includes = ["."], + visibility = [ + "//score/crypto:__subpackages__", + "//score/mw/crypto:__subpackages__", + ], + deps = [ + "@score_baselibs//score/result", + ], +) diff --git a/score/mw/crypto/api/common/crypto_resource_guard.hpp b/score/mw/crypto/api/common/crypto_resource_guard.hpp new file mode 100644 index 0000000..8644a95 --- /dev/null +++ b/score/mw/crypto/api/common/crypto_resource_guard.hpp @@ -0,0 +1,144 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_COMMON_CRYPTO_RESOURCE_GUARD_HPP +#define SCORE_MW_CRYPTO_API_COMMON_CRYPTO_RESOURCE_GUARD_HPP + +#include "score/mw/crypto/api/common/types.hpp" +#include "score/result/result.h" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +// Forward declaration enabling internal guard construction +class CryptoResourceGuardFactory; + +/// @brief RAII guard for transient CryptoResourceId handles returned by resource-producing operations. +/// +/// Returned by resource-producing methods (GenerateKey, DeriveKey, AgreeKey, +/// UnwrapKey, ImportKey, LoadKey, LoadCertificatePublicKey, ImportCrl). The guard owns +/// the ephemeral resource and releases it to the daemon when it goes out of +/// scope or when Release() is called. +/// +/// **CryptoResourceGuard is move-only** — copying would alias ownership of +/// the same daemon resource, causing a double-release. +/// +/// **Slot-direct path** (no guard needed): +/// @code +/// auto slot = ctx->ResolveResource("MyKey", ResourceType::kKeySlot).value(); +/// CipherContextConfig config; +/// config.SetAlgorithm("AES-256-CBC").SetKey(slot).SetDirection(CipherDirection::kEncrypt); +/// auto cipher = ctx->CreateCipherContext(config).value(); +/// // Context loads and releases key material internally. +/// @endcode +/// +/// **Guard path** (for generated/derived/loaded/imported resources): +/// @code +/// auto guard = key_mgmt->GenerateKey(GenerateKeyParams{}.SetAlgorithm("AES-256")).value(); +/// CipherContextConfig config; +/// config.SetAlgorithm("AES-256-GCM").SetKey(guard).SetDirection(CipherDirection::kEncrypt); +/// // guard must remain alive until CreateCipherContext() returns. +/// auto cipher = ctx->CreateCipherContext(config).value(); +/// // Daemon has bound the key to the context — guard may now be destroyed. +/// +/// // PersistKey copies the key to persistent storage. +/// // The guard remains active and releases the ephemeral copy independently. +/// key_mgmt->PersistKey(guard, slot).value(); +/// // guard still active; ephemeral copy released when guard goes out of scope. +/// @endcode +/// +/// For explicit synchronous release with error handling, call Release() +/// before the guard is destroyed. The destructor silently swallows errors +/// because destructors must not propagate exceptions. + +class CryptoResourceGuard +{ + public: + /// @brief Destructor. Releases the transient resource if still active. + ~CryptoResourceGuard() noexcept; + + // Non-copyable — would create aliased ownership leading to double-release. + CryptoResourceGuard(const CryptoResourceGuard&) = delete; + CryptoResourceGuard& operator=(const CryptoResourceGuard&) = delete; + + /// @brief Move constructor. Transfers ownership; moved-from guard becomes inactive. + CryptoResourceGuard(CryptoResourceGuard&& other) noexcept; + + /// @brief Move assignment. Releases current resource (if active), then transfers. + CryptoResourceGuard& operator=(CryptoResourceGuard&& other) noexcept; + + /// @brief Returns a const reference to the underlying CryptoResourceId. + /// @pre Guard must be active (IsActive() == true). + const CryptoResourceId& Id() const noexcept; + + /// @brief Implicit conversion to const CryptoResourceId&. + /// + /// Enables passing a CryptoResourceGuard directly to any API that + /// accepts `const CryptoResourceId&` (e.g., SetKey(), ExportKey(), + /// PersistKey()) without requiring overloads or explicit .Id() calls. + // NOLINTNEXTLINE(google-explicit-constructor) + operator const CryptoResourceId&() const noexcept; + + /// @brief Explicitly releases the transient resource with synchronous error feedback. + /// + /// Use this when release must happen at a specific point or when errors must + /// be handled. On success, the guard becomes inactive and the destructor is + /// a no-op. + /// + /// @code + /// auto result = guard.Release(); + /// if (!result.has_value()) { + /// // Release failed — e.g., daemon still has active contexts using this key. + /// } + /// @endcode + /// + /// @return std::monostate on success; error if the guard is already inactive. + score::Result Release() noexcept; + + /// @brief Returns whether the guard still owns a resource. + /// + /// Returns false for moved-from guards and guards on which Release() succeeded. + /// Useful for assertions: the guard must return true at Create*Context() time. + bool IsActive() const noexcept; + + private: + // CryptoResourceGuardFactory is the sole construction path for guards. It is an + // internal implementation detail + friend class CryptoResourceGuardFactory; + + /// @brief Private constructor — only callable by CryptoResourceGuardFactory. + CryptoResourceGuard(std::shared_ptr release_handle, CryptoResourceId id) noexcept; + + /// @brief Type-erased release handle. Internally holds a shared_ptr + /// constructed by the IPC layer. Not accessible from application code. + std::shared_ptr release_handle_; + + /// @brief The guarded resource handle. + CryptoResourceId id_; + + /// @brief Whether this guard still owns the resource. + bool active_; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_COMMON_CRYPTO_RESOURCE_GUARD_HPP diff --git a/score/mw/crypto/api/common/error_domain.hpp b/score/mw/crypto/api/common/error_domain.hpp new file mode 100644 index 0000000..41ffe37 --- /dev/null +++ b/score/mw/crypto/api/common/error_domain.hpp @@ -0,0 +1,150 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_COMMON_ERROR_DOMAIN_HPP +#define SCORE_MW_CRYPTO_API_COMMON_ERROR_DOMAIN_HPP + +#include "score/result/result.h" + +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Error codes for the crypto stack, organized by category. +/// +/// Hex categories follow the pattern 0x01CCxxxx where CC is the category: +/// 01 = General / initialization errors +/// 02 = Operation errors +/// 03 = Parameter / validation errors +/// 04 = Streaming errors +/// 05 = Algorithm errors +/// 06 = Memory / resource errors +/// 07 = Context errors +/// 08 = Key management errors +/// 09 = Certificate errors +/// 0A = Provider errors +/// 0B = Access control errors +enum class CryptoErrorCode : score::result::ErrorCode +{ + // ---- General / initialization errors (0x01010000) ---- + kUninitializedStack = 0x01010001, ///< Stack not initialized + kAlreadyInitialized = 0x01010002, ///< Stack already initialized + kConnectionFailed = 0x01010003, ///< Failed to connect to daemon + kInternalError = 0x01010004, ///< Unspecified internal error + + // ---- Operation errors (0x01020000) ---- + kUnsupportedOperation = 0x01020001, ///< Operation not supported by provider + kInvalidOperation = 0x01020002, ///< Operation not valid in current state + kOperationFailed = 0x01020003, ///< Operation execution failed + kOperationTimedOut = 0x01020004, ///< Operation exceeded configured timeout deadline + + // ---- Parameter / validation errors (0x01030000) ---- + kInvalidArgument = 0x01030001, ///< Invalid argument provided + kResourceNotFound = 0x01030002, ///< Requested resource not found + kInvalidResourceId = 0x01030003, ///< Resource ID does not resolve to a valid resource + kInvalidResourceType = 0x01030004, ///< Resource type mismatch + kInsufficientBufferSize = 0x01030005, ///< Output buffer too small + kInvalidFormat = 0x01030006, ///< Data format not recognized (DER/PEM) + kParamTruncated = 0x01030007, ///< Input parameter exceeded fixed-capacity storage and was + ///< silently truncated (e.g. KDF salt or seed too long) + + // ---- Streaming errors (0x01040000) ---- + kStreamNotInitialized = 0x01040001, ///< Init() not called before Update()/Finalize() + kStreamAlreadyActive = 0x01040002, ///< Init() called while stream is active + kStreamIncomplete = 0x01040003, ///< Finalize() called without sufficient data + + // ---- Algorithm errors (0x01050000) ---- + kUnsupportedAlgorithm = 0x01050001, ///< Algorithm not supported by any configured provider + kAlgorithmMismatch = 0x01050002, ///< Algorithm incompatible with key/slot + + // ---- Memory / resource errors (0x01060000) ---- + kAllocationFailed = 0x01060001, ///< Shared memory allocation failed + kQuotaExceeded = 0x01060002, ///< Per-application memory quota exceeded + kInvalidMemoryRegion = 0x01060003, ///< Memory region is invalid or already released + + // ---- Context errors (0x01070000) ---- + kContextCreationFailed = 0x01070001, ///< Failed to create operation context + kContextAlreadyDestroyed = 0x01070002, ///< Context used after destruction + kContextResetFailed = 0x01070003, ///< Failed to reset context to initial state + kSessionExpired = 0x01070004, ///< Guard's session sentinel expired (context destroyed); + ///< logged for diagnostics, daemon bulk-cleans via EndSession + + // ---- Key management errors (0x01080000) ---- + kKeySlotEmpty = 0x01080001, ///< Key slot contains no key material + kKeySlotOccupied = 0x01080002, ///< Key slot already occupied (for persist/import) + kKeyNotExportable = 0x01080003, ///< Key cannot be exported from its provider + kKeyGenerationFailed = 0x01080004, ///< Key generation failed + kKeyDerivationFailed = 0x01080005, ///< Key derivation failed + kKeyAgreementFailed = 0x01080006, ///< Key agreement failed + kWrapUnwrapFailed = 0x01080007, ///< Key wrap/unwrap operation failed + kPersistFailed = 0x01080008, ///< Failed to persist ephemeral key + kIncompatibleKeyType = 0x01080009, ///< Key type incompatible with requested operation + kKeyOperationNotPermitted = 0x0108000A, ///< Key's permitted operations do not include the + ///< requested operation (e.g., encrypt-only key + ///< used for signing) + + // ---- Certificate errors (0x01090000) ---- + kCertificateParsingFailed = 0x01090001, ///< Certificate data could not be parsed + kCertificateExpired = 0x01090002, ///< Certificate has expired + kCertificateRevoked = 0x01090003, ///< Certificate has been revoked + kCertificateVerifyFailed = 0x01090004, ///< Certificate verification failed + kCertChainVerifyFailed = 0x01090005, ///< Certificate chain verification failed + kCrlImportFailed = 0x01090006, ///< CRL import failed + kCsrGenerationFailed = 0x01090007, ///< CSR generation failed + kOcspError = 0x01090008, ///< OCSP request/response error + kTrustAnchorNotFound = 0x01090009, ///< Trust anchor resource not found or empty + + // ---- Provider errors (0x010A0000) ---- + kProviderNotAvailable = 0x010A0001, ///< Requested provider is not available + kProviderBusy = 0x010A0002, ///< Provider is temporarily busy + kCrossProviderIncompatible = 0x010A0003, ///< Resource cannot be used with the target provider + + // ---- Access control errors (0x010B0000) ---- + kAccessDenied = 0x010B0001, ///< Application not authorized for this resource + kResourceNotAllocated = 0x010B0002, ///< Resource not allocated to this application +}; + +/// @brief Error domain for the crypto stack. +/// +/// Follows the score::result::ErrorDomain pattern. A single constexpr instance +/// is used throughout the crypto API. +class CryptoErrorDomain final : public score::result::ErrorDomain +{ + public: + /// @brief Returns a human-readable message for the given error code. + std::string_view MessageFor(const score::result::ErrorCode& code) const noexcept override; +}; + +/// @brief Global constexpr instance of the crypto error domain. +constexpr CryptoErrorDomain kCryptoErrorDomain{}; + +/// @brief Creates an Error from a CryptoErrorCode and optional user message. +/// @param code The crypto error code +/// @param user_message Optional additional context for the error +/// @return A score::result::Error bound to the crypto error domain +/// +/// Inline definition to support ADL in result.h templates +inline score::result::Error MakeError(CryptoErrorCode code, std::string_view user_message = "") noexcept +{ + return {static_cast(code), kCryptoErrorDomain, user_message}; +} + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_COMMON_ERROR_DOMAIN_HPP diff --git a/score/mw/crypto/api/common/fixed_capacity_string.hpp b/score/mw/crypto/api/common/fixed_capacity_string.hpp new file mode 100644 index 0000000..e6a3c4a --- /dev/null +++ b/score/mw/crypto/api/common/fixed_capacity_string.hpp @@ -0,0 +1,327 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_COMMON_FIXED_CAPACITY_STRING_HPP +#define SCORE_MW_CRYPTO_API_COMMON_FIXED_CAPACITY_STRING_HPP + +#include +#include +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief A fixed-capacity, stack-allocated string that never heap-allocates. +/// +/// @par Usage +/// @code +/// FixedCapacityString<64> alg{"AES-256-CBC"}; +/// alg = "ML-KEM-768"; // reassignment, still no allocation +/// std::string_view sv = alg; // implicit conversion +/// assert(alg == "SHA-256"); // comparison with string literals +/// @endcode +template +class FixedCapacityString +{ + public: + // TODO: Consider adding a static_assert to enforce a reasonable maximum capacity (e.g., N <= 256) to prevent + // misuse. + // static_assert(N > 0U, "FixedCapacityString must have a positive capacity"); + // static_assert(N <= 256U, "FixedCapacityString capacity should be reasonably small to avoid misuse"); + // This would help catch errors at compile time if someone tries to create an excessively large + // FixedCapacityString. + // TODO: mention additional methods from std::string that should be supported to make it easier to use. + // TODO: Add a Create method that returns a std::optional to handle truncation more explicitly, + // if desired. + + /// @brief Maximum number of characters this string can hold. + static constexpr std::size_t max_capacity = N; + + /// @brief Default constructor — empty string. + constexpr FixedCapacityString() noexcept : size_{0U}, truncated_{false} + { + data_[0U] = '\0'; + } + + /// @brief Constructs from a null-terminated C string. + /// @param str Null-terminated string. If strlen(str) > N, the input is + /// silently truncated to N characters and truncated() returns true. + /// + /// @par Safety + /// No exceptions — truncation is deterministic and bounded. Use + /// truncated() after construction to detect data loss in debug builds. + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr FixedCapacityString(const char* str) noexcept : size_{0U}, truncated_{false} + { + if (str != nullptr) + { + const std::size_t len = const_strlen(str); + if (len > N) + { + size_ = N; + truncated_ = true; + } + else + { + size_ = len; + } + for (std::size_t i = 0U; i < size_; ++i) + { + data_[i] = str[i]; + } + } + data_[size_] = '\0'; + } + + /// @brief Constructs from a std::string_view. + /// @param sv String view. If sv.size() > N, the input is silently + /// truncated to N characters and truncated() returns true. + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr FixedCapacityString(std::string_view sv) noexcept : size_{0U}, truncated_{false} + { + if (sv.size() > N) + { + size_ = N; + truncated_ = true; + } + else + { + size_ = sv.size(); + } + for (std::size_t i = 0U; i < size_; ++i) + { + data_[i] = sv[i]; + } + data_[size_] = '\0'; + } + + /// @brief Constructs from a std::string. + /// @param s String. If s.size() > N, the input is silently truncated. + // NOLINTNEXTLINE(google-explicit-constructor) + FixedCapacityString(const std::string& s) noexcept : FixedCapacityString(std::string_view{s}) {} + + // -- Defaulted copy/move (trivial — no heap resources) -- + + constexpr FixedCapacityString(const FixedCapacityString&) = default; + constexpr FixedCapacityString& operator=(const FixedCapacityString&) = default; + constexpr FixedCapacityString(FixedCapacityString&&) noexcept = default; + constexpr FixedCapacityString& operator=(FixedCapacityString&&) noexcept = default; + ~FixedCapacityString() = default; + + /// @brief Assigns from a C string. Truncates if strlen(str) > N. + FixedCapacityString& operator=(const char* str) noexcept + { + *this = FixedCapacityString{str}; + return *this; + } + + /// @brief Assigns from a string_view. Truncates if sv.size() > N. + FixedCapacityString& operator=(std::string_view sv) noexcept + { + *this = FixedCapacityString{sv}; + return *this; + } + + /// @brief Assigns from a std::string. Truncates if s.size() > N. + FixedCapacityString& operator=(const std::string& s) noexcept + { + *this = FixedCapacityString{std::string_view{s}}; + return *this; + } + + // -- Accessors -- + + /// @brief Returns a pointer to the null-terminated character data. + constexpr const char* c_str() const noexcept + { + return data_.data(); + } + + /// @brief Returns a pointer to the character data. + constexpr const char* data() const noexcept + { + return data_.data(); + } + + /// @brief Returns the number of characters (excluding null terminator). + constexpr std::size_t size() const noexcept + { + return size_; + } + + /// @brief Returns the number of characters (excluding null terminator). + constexpr std::size_t length() const noexcept + { + return size_; + } + + /// @brief Returns true if the string is empty. + constexpr bool empty() const noexcept + { + return size_ == 0U; + } + + /// @brief Returns true if the last assignment/construction truncated input. + /// + /// Use this to detect data loss after construction or assignment. + /// In debug builds, callers may assert on this value for early detection + /// of capacity mismatches. In production, truncation is a deterministic, + /// bounded operation — no undefined behaviour occurs. + /// + /// @code + /// AlgorithmId alg{"VERY-LONG-ALGORITHM-NAME-THAT-EXCEEDS-64-CHARS..."}; + /// if (alg.truncated()) { + /// // handle data loss — algorithm name was truncated + /// } + /// @endcode + constexpr bool truncated() const noexcept + { + return truncated_; + } + + /// @brief Returns the maximum number of characters this string can hold. + static constexpr std::size_t capacity() noexcept + { + return N; + } + + // -- Conversions -- + + /// @brief Implicit conversion to std::string_view. + /// + /// This is the primary interop mechanism. Enables passing a + /// FixedCapacityString to any API that accepts std::string_view + /// without allocation. + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr operator std::string_view() const noexcept + { + return std::string_view{data_.data(), size_}; + } + + /// @brief Explicit conversion to std::string. + /// + /// This allocates — use only when a std::string is required + /// (e.g., for IPC serialization). + explicit operator std::string() const + { + return std::string{data_.data(), size_}; + } + + // -- Comparison operators -- + + constexpr bool operator==(const FixedCapacityString& other) const noexcept + { + if (size_ != other.size_) + { + return false; + } + for (std::size_t i = 0U; i < size_; ++i) + { + if (data_[i] != other.data_[i]) + { + return false; + } + } + return true; + } + + constexpr bool operator!=(const FixedCapacityString& other) const noexcept + { + return !(*this == other); + } + + constexpr bool operator==(std::string_view sv) const noexcept + { + return std::string_view{data_.data(), size_} == sv; + } + + constexpr bool operator!=(std::string_view sv) const noexcept + { + return !(*this == sv); + } + + constexpr bool operator==(const char* str) const noexcept + { + return std::string_view{data_.data(), size_} == std::string_view{str}; + } + + constexpr bool operator!=(const char* str) const noexcept + { + return !(*this == str); + } + + bool operator==(const std::string& s) const noexcept + { + return std::string_view{data_.data(), size_} == std::string_view{s}; + } + + bool operator!=(const std::string& s) const noexcept + { + return !(*this == s); + } + + // -- Relational operators (for use in ordered containers) -- + + constexpr bool operator<(const FixedCapacityString& other) const noexcept + { + return std::string_view{data_.data(), size_} < std::string_view{other.data_.data(), other.size_}; + } + + private: + /// @brief Compile-time strlen for constexpr construction. + static constexpr std::size_t const_strlen(const char* str) noexcept + { + // TODO: Improve to avoid any potential out-of-bounds access if str is not null-terminated within N+1 + // characters. For example, we could add a check to ensure we don't read beyond N characters: + std::size_t len = 0U; + while (str[len] != '\0') + { + ++len; + } + return len; + } + + /// @brief Internal storage: N characters + null terminator. + std::array data_{}; + + /// @brief Current string length (excluding null terminator). + std::size_t size_{0U}; + + /// @brief Whether the last construction/assignment truncated the input. + bool truncated_{false}; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +/// @brief std::hash specialization for FixedCapacityString. +/// +/// Enables use as key in std::unordered_map and std::unordered_set. +/// Delegates to std::hash for consistent hashing. +template +struct std::hash> +{ + std::size_t operator()(const score::mw::crypto::FixedCapacityString& s) const noexcept + { + return std::hash{}(std::string_view{s}); + } +}; + +#endif // SCORE_MW_CRYPTO_API_COMMON_FIXED_CAPACITY_STRING_HPP diff --git a/score/mw/crypto/api/common/i_memory_allocator.hpp b/score/mw/crypto/api/common/i_memory_allocator.hpp new file mode 100644 index 0000000..0f97d94 --- /dev/null +++ b/score/mw/crypto/api/common/i_memory_allocator.hpp @@ -0,0 +1,85 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_COMMON_I_MEMORY_ALLOCATOR_HPP +#define SCORE_MW_CRYPTO_API_COMMON_I_MEMORY_ALLOCATOR_HPP + +#include "score/mw/crypto/api/common/i_memory_region.hpp" +#include "score/mw/crypto/api/common/types.hpp" +#include "score/result/result.h" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Memory allocator interface for the data plane. +/// +/// The daemon enforces per-application quotas. Allocation may fail if the +/// quota is exceeded or the requested memory type is not available. +/// +/// @see dec_rec__crypto__memory_allocator_separation for the rationale +/// behind separating this interface from ICryptoStack. +// TODO: What would be the initial values on the allocated memory? Zeroed out by default? Uninitialized? This should be +// documented and consistent across implementations. +// TODO: Should there be a choice for the user to decide on the allocation behavior (e.g., zero-initialized vs. +// uninitialized) to allow for performance optimizations when zeroing is not needed? +class IMemoryAllocator +{ + public: + using Uptr = std::unique_ptr; + + virtual ~IMemoryAllocator() = default; + + IMemoryAllocator(const IMemoryAllocator&) = delete; + IMemoryAllocator& operator=(const IMemoryAllocator&) = delete; + IMemoryAllocator(IMemoryAllocator&&) = default; + IMemoryAllocator& operator=(IMemoryAllocator&&) = default; + + /// @brief Allocates shared memory with kDefault type. + /// @param size Number of bytes to allocate + /// @return Writable memory region on success, error on failure + /// @note Daemon tracks this against per-application quota. + virtual score::Result Allocate(std::size_t size) = 0; + + /// @brief Allocates provider-compatible shared memory. + /// @param size Number of bytes to allocate + /// @param type Memory type (kDefault or kProviderCompatible) + /// @param provider Resolved provider handle for provider-compatible allocation + /// @return Writable memory region on success, error on failure + /// @note Enables zero-copy path from application to crypto device when + /// kProviderCompatible is used with the correct provider. + virtual score::Result Allocate(std::size_t size, + MemoryType type, + const CryptoResourceId& provider) = 0; + + /// @brief Returns the maximum allocation permitted for this application. + /// @return Quota in bytes (daemon-configured, overridable per app) + virtual std::size_t GetQuota() const noexcept = 0; + + /// @brief Returns the currently allocated bytes for this application. + virtual std::size_t GetCurrentUsage() const noexcept = 0; + + protected: + IMemoryAllocator() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_COMMON_I_MEMORY_ALLOCATOR_HPP diff --git a/score/mw/crypto/api/common/i_memory_region.hpp b/score/mw/crypto/api/common/i_memory_region.hpp new file mode 100644 index 0000000..493605d --- /dev/null +++ b/score/mw/crypto/api/common/i_memory_region.hpp @@ -0,0 +1,103 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_COMMON_I_MEMORY_REGION_HPP +#define SCORE_MW_CRYPTO_API_COMMON_I_MEMORY_REGION_HPP + +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Immutable view into an allocated shared memory region (data plane). +/// +/// Represents a read-only window into shared memory managed by the daemon. +/// Used as input to all operation contexts. The underlying memory is shared +/// between the library and daemon, avoiding copies across the IPC boundary. +/// +/// Destruction releases the shared memory segment back to the daemon's pool +/// and decrements the per-application quota. +class IReadOnlyMemoryRegion +{ + public: + using Uptr = std::unique_ptr; + + virtual ~IReadOnlyMemoryRegion() = default; + + IReadOnlyMemoryRegion(const IReadOnlyMemoryRegion&) = delete; + IReadOnlyMemoryRegion& operator=(const IReadOnlyMemoryRegion&) = delete; + IReadOnlyMemoryRegion(IReadOnlyMemoryRegion&&) = default; + IReadOnlyMemoryRegion& operator=(IReadOnlyMemoryRegion&&) = default; + + /// @brief Returns a pointer to the beginning of the memory region. + virtual const uint8_t* data() const noexcept = 0; + + /// @brief Returns the size of the memory region in bytes. + virtual std::size_t size() const noexcept = 0; + + /// @brief Returns an immutable span over the memory region. + /// @note Convenience wrapper: equivalent to span{data(), size()}. + virtual score::cpp::span AsSpan() const noexcept = 0; + + protected: + IReadOnlyMemoryRegion() = default; +}; + +/// @brief Mutable view into an allocated shared memory region (data plane). +/// +/// Extends IReadOnlyMemoryRegion with write access. Used for both writing +/// input data into shared memory and receiving output from operations. +/// The underlying memory is shared between library and daemon. +/// +/// Destruction releases the shared memory segment back to the daemon's pool +/// and decrements the per-application quota. +class IReadWriteMemoryRegion : public IReadOnlyMemoryRegion +{ + public: + using Uptr = std::unique_ptr; + + ~IReadWriteMemoryRegion() override = default; + + IReadWriteMemoryRegion(const IReadWriteMemoryRegion&) = delete; + IReadWriteMemoryRegion& operator=(const IReadWriteMemoryRegion&) = delete; + IReadWriteMemoryRegion(IReadWriteMemoryRegion&&) = default; + IReadWriteMemoryRegion& operator=(IReadWriteMemoryRegion&&) = default; + + /// @brief Returns a mutable pointer to the beginning of the memory region. + virtual uint8_t* data() noexcept = 0; + + /// @brief Returns a mutable span over the memory region. + virtual score::cpp::span AsWritableSpan() noexcept = 0; + + /// @brief Resizes the memory region. + /// @param new_size The desired size in bytes + /// @return std::monostate on success, error if the new size exceeds quota or is invalid + /// @note May invalidate previously obtained pointers/spans. + virtual score::Result Resize(std::size_t new_size) = 0; + + protected: + IReadWriteMemoryRegion() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_COMMON_I_MEMORY_REGION_HPP diff --git a/score/mw/crypto/api/common/src/crypto_resource_guard.cpp b/score/mw/crypto/api/common/src/crypto_resource_guard.cpp new file mode 100644 index 0000000..8429c23 --- /dev/null +++ b/score/mw/crypto/api/common/src/crypto_resource_guard.cpp @@ -0,0 +1,103 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/mw/crypto/api/common/crypto_resource_guard.hpp" +#include "score/mw/crypto/api/common/error_domain.hpp" +#include "score/mw/crypto/api/common/src/i_release_callback.hpp" + +#include +#include +namespace score +{ +namespace mw +{ +namespace crypto +{ +CryptoResourceGuard::CryptoResourceGuard(std::shared_ptr release_handle, CryptoResourceId id) noexcept + : release_handle_{std::move(release_handle)}, id_{id}, active_{true} +{ +} + +CryptoResourceGuard::~CryptoResourceGuard() noexcept +{ + if (active_ && release_handle_) + { + // static_cast is safe: release_handle_ is always constructed from + // shared_ptr via implicit conversion in MakeGuard. + // Silently swallow errors — destructors must not propagate. + auto* cb = static_cast(release_handle_.get()); + static_cast(cb->ReleaseResource(id_)); + } +} + +CryptoResourceGuard::CryptoResourceGuard(CryptoResourceGuard&& other) noexcept + : release_handle_{std::move(other.release_handle_)}, id_{other.id_}, active_{other.active_} +{ + other.active_ = false; +} + +CryptoResourceGuard& CryptoResourceGuard::operator=(CryptoResourceGuard&& other) noexcept +{ + if (this != &other) + { + if (active_ && release_handle_) + { + auto* cb = static_cast(release_handle_.get()); + static_cast(cb->ReleaseResource(id_)); + } + release_handle_ = std::move(other.release_handle_); + id_ = other.id_; + active_ = other.active_; + other.active_ = false; + } + return *this; +} + +const CryptoResourceId& CryptoResourceGuard::Id() const noexcept +{ + assert(active_ && + "CryptoResourceGuard::Id() called on an inactive guard " + "(moved-from, Released, or default-constructed)"); + return id_; +} + +CryptoResourceGuard::operator const CryptoResourceId&() const noexcept +{ + assert(active_ && "CryptoResourceGuard implicit conversion called on an inactive guard"); + return id_; +} + +bool CryptoResourceGuard::IsActive() const noexcept +{ + return active_; +} + +score::Result CryptoResourceGuard::Release() noexcept +{ + if (!active_ || !release_handle_) + { + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kInvalidResourceId, "Release() called on an inactive guard")}; + } + auto* cb = static_cast(release_handle_.get()); + auto result = cb->ReleaseResource(id_); + if (result.has_value()) + { + active_ = false; // guard no longer owns the resource + release_handle_.reset(); // destructor will be a no-op + } + return result; +} + +} // namespace crypto +} // namespace mw +} // namespace score diff --git a/score/mw/crypto/api/common/src/crypto_resource_guard_factory.hpp b/score/mw/crypto/api/common/src/crypto_resource_guard_factory.hpp new file mode 100644 index 0000000..3d2617b --- /dev/null +++ b/score/mw/crypto/api/common/src/crypto_resource_guard_factory.hpp @@ -0,0 +1,107 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_COMMON_SRC_CRYPTO_RESOURCE_GUARD_FACTORY_HPP +#define SCORE_MW_CRYPTO_API_COMMON_SRC_CRYPTO_RESOURCE_GUARD_FACTORY_HPP + +/// @file +/// @brief Internal factory for constructing CryptoResourceGuard instances. +/// +/// **This is an internal header — never include it from application or public +/// interface code.** It belongs in `src/` alongside `i_release_callback.hpp` +/// and is only included by concrete IPC-layer implementations that produce +/// guards on behalf of key-producing context methods. +/// +/// ## Design +/// +/// `CryptoResourceGuard` has a private constructor to prevent arbitrary +/// construction in application code. `CryptoResourceGuardFactory` is declared +/// as a `friend` of `CryptoResourceGuard` so that it can call that private +/// constructor. Because this header lives only in `src/` (not under the public +/// include root), no application code can ever obtain or instantiate the +/// factory. +/// +/// ## How the type erasure works +/// +/// The release handle is stored as `std::shared_ptr` inside the guard, +/// but is always constructed from a `std::shared_ptr`. +/// The implicit upcast (`shared_ptr` → `shared_ptr`) +/// preserves the deleter, so the pointed-to object is correctly destroyed. +/// The destructor of `CryptoResourceGuard` recovers the concrete pointer via +/// `static_cast`, which is safe because `release_handle_` +/// is always set through this factory and never any other way. +/// +/// ## Usage in a concrete implementation +/// +/// @code +/// #include "score/mw/crypto/api/common/src/crypto_resource_guard_factory.hpp" +/// #include "score/mw/crypto/api/common/src/i_release_callback.hpp" +/// +/// class ConcreteKeyMgmt : public score::mw::crypto::IKeyManagementContext { +/// public: +/// score::Result +/// GenerateKey(const score::mw::crypto::GenerateKeyParams& params) override +/// { +/// // 1. IPC: send GenerateKey to daemon, receive assigned resource id +/// score::mw::crypto::CryptoResourceId id = /* IPC result */; +/// +/// // 2. ipc_release_cb_ is std::shared_ptr +/// // Implicit conversion to shared_ptr happens here. +/// return score::mw::crypto::CryptoResourceGuardFactory::Make( +/// ipc_release_cb_, id); +/// } +/// +/// private: +/// std::shared_ptr ipc_release_cb_; +/// }; +/// @endcode + +#include "score/mw/crypto/api/common/crypto_resource_guard.hpp" + +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Internal factory for CryptoResourceGuard construction. +/// +/// Declared as a friend of `CryptoResourceGuard`. Concrete IPC-layer +/// implementations call `CryptoResourceGuardFactory::Make()` to produce guards. +/// Application code has no access to this class. +class CryptoResourceGuardFactory +{ + public: + /// @brief Constructs a guard that owns a daemon-assigned transient resource. + /// + /// @param release_handle `shared_ptr` obtained from the + /// IPC layer, implicitly converted to `shared_ptr` at the call + /// site. Must not be null. + /// @param id The transient resource handle assigned by the daemon. + /// @return An active CryptoResourceGuard; IsActive() == true. + static CryptoResourceGuard Make(std::shared_ptr release_handle, CryptoResourceId id) noexcept + { + return CryptoResourceGuard{std::move(release_handle), id}; + } + + private: + CryptoResourceGuardFactory() = delete; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_COMMON_SRC_CRYPTO_RESOURCE_GUARD_FACTORY_HPP diff --git a/score/mw/crypto/api/common/src/error_domain.cpp b/score/mw/crypto/api/common/src/error_domain.cpp new file mode 100644 index 0000000..de30c13 --- /dev/null +++ b/score/mw/crypto/api/common/src/error_domain.cpp @@ -0,0 +1,156 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/mw/crypto/api/common/error_domain.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +std::string_view CryptoErrorDomain::MessageFor(const score::result::ErrorCode& code) const noexcept +{ + const auto crypto_code = static_cast(code); + switch (crypto_code) + { + // General / initialization + case CryptoErrorCode::kUninitializedStack: + return "Crypto stack not initialized"; + case CryptoErrorCode::kAlreadyInitialized: + return "Crypto stack already initialized"; + case CryptoErrorCode::kConnectionFailed: + return "Failed to connect to crypto daemon"; + case CryptoErrorCode::kInternalError: + return "Internal error"; + + // Operation + case CryptoErrorCode::kUnsupportedOperation: + return "Unsupported operation"; + case CryptoErrorCode::kInvalidOperation: + return "Invalid operation in current state"; + case CryptoErrorCode::kOperationFailed: + return "Operation failed"; + case CryptoErrorCode::kOperationTimedOut: + return "Operation exceeded configured timeout deadline"; + + // Parameter / validation + case CryptoErrorCode::kInvalidArgument: + return "Invalid argument"; + case CryptoErrorCode::kInvalidResourceId: + return "Invalid resource identifier"; + case CryptoErrorCode::kInvalidResourceType: + return "Resource type mismatch"; + case CryptoErrorCode::kInsufficientBufferSize: + return "Output buffer too small"; + case CryptoErrorCode::kInvalidFormat: + return "Invalid data format"; + case CryptoErrorCode::kParamTruncated: + return "Parameter truncated: input exceeds fixed-capacity storage (reduce salt or seed size)"; + + // Streaming + case CryptoErrorCode::kStreamNotInitialized: + return "Stream not initialized"; + case CryptoErrorCode::kStreamAlreadyActive: + return "Stream already active"; + case CryptoErrorCode::kStreamIncomplete: + return "Stream incomplete"; + + // Algorithm + case CryptoErrorCode::kUnsupportedAlgorithm: + return "Unsupported algorithm"; + case CryptoErrorCode::kAlgorithmMismatch: + return "Algorithm mismatch with key or slot"; + + // Memory / resource + case CryptoErrorCode::kAllocationFailed: + return "Memory allocation failed"; + case CryptoErrorCode::kQuotaExceeded: + return "Memory quota exceeded"; + case CryptoErrorCode::kInvalidMemoryRegion: + return "Invalid memory region"; + + // Context + case CryptoErrorCode::kContextCreationFailed: + return "Context creation failed"; + case CryptoErrorCode::kContextAlreadyDestroyed: + return "Context already destroyed"; + case CryptoErrorCode::kContextResetFailed: + return "Context reset failed"; + case CryptoErrorCode::kSessionExpired: + return "Session expired (context destroyed)"; + + // Key management + case CryptoErrorCode::kKeySlotEmpty: + return "Key slot is empty"; + case CryptoErrorCode::kKeySlotOccupied: + return "Key slot is already occupied"; + case CryptoErrorCode::kKeyNotExportable: + return "Key is not exportable"; + case CryptoErrorCode::kKeyGenerationFailed: + return "Key generation failed"; + case CryptoErrorCode::kKeyDerivationFailed: + return "Key derivation failed"; + case CryptoErrorCode::kKeyAgreementFailed: + return "Key agreement failed"; + case CryptoErrorCode::kWrapUnwrapFailed: + return "Key wrap/unwrap failed"; + case CryptoErrorCode::kPersistFailed: + return "Key persist failed"; + case CryptoErrorCode::kIncompatibleKeyType: + return "Incompatible key type"; + case CryptoErrorCode::kKeyOperationNotPermitted: + return "Key operation not permitted by slot policy"; + + // Certificate + case CryptoErrorCode::kCertificateParsingFailed: + return "Certificate parsing failed"; + case CryptoErrorCode::kCertificateExpired: + return "Certificate expired"; + case CryptoErrorCode::kCertificateRevoked: + return "Certificate revoked"; + case CryptoErrorCode::kCertificateVerifyFailed: + return "Certificate verification failed"; + case CryptoErrorCode::kCertChainVerifyFailed: + return "Certificate chain verification failed"; + case CryptoErrorCode::kCrlImportFailed: + return "CRL import failed"; + case CryptoErrorCode::kCsrGenerationFailed: + return "CSR generation failed"; + case CryptoErrorCode::kOcspError: + return "OCSP error"; + case CryptoErrorCode::kTrustAnchorNotFound: + return "Trust anchor not found"; + + // Provider + case CryptoErrorCode::kProviderNotAvailable: + return "Provider not available"; + case CryptoErrorCode::kProviderBusy: + return "Provider busy"; + case CryptoErrorCode::kCrossProviderIncompatible: + return "Cross-provider incompatible"; + + // Access control + case CryptoErrorCode::kAccessDenied: + return "Access denied"; + case CryptoErrorCode::kResourceNotAllocated: + return "Resource not allocated to application"; + + default: + return "Unrecognized crypto error code"; + } +} + +} // namespace crypto +} // namespace mw +} // namespace score diff --git a/score/mw/crypto/api/common/src/i_release_callback.hpp b/score/mw/crypto/api/common/src/i_release_callback.hpp new file mode 100644 index 0000000..4f7cc88 --- /dev/null +++ b/score/mw/crypto/api/common/src/i_release_callback.hpp @@ -0,0 +1,66 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_COMMON_SRC_I_RELEASE_CALLBACK_HPP +#define SCORE_MW_CRYPTO_API_COMMON_SRC_I_RELEASE_CALLBACK_HPP + +#include "score/mw/crypto/api/common/types.hpp" +#include "score/result/result.h" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Narrow callback interface for releasing transient crypto resources. +/// +/// Used internally by CryptoResourceGuard to dispatch Release IPC calls to +/// the daemon. Concrete implementations (in the IPC layer) hold the daemon +/// connection and translate ReleaseResource() into a protocol message. +class IReleaseCallback +{ + public: + /// @brief Destructor. + virtual ~IReleaseCallback() = default; + + IReleaseCallback(const IReleaseCallback&) = delete; + IReleaseCallback& operator=(const IReleaseCallback&) = delete; + IReleaseCallback(IReleaseCallback&&) = default; + IReleaseCallback& operator=(IReleaseCallback&&) = default; + + // TODO: What shall be the deallocation strategy? zeroing, random filling etc? + // TODO: Do we need to consider user input to determine the cleanup strategy and hence exposing an API? + /// @brief Releases a single transient crypto resource. + /// + /// Called by CryptoResourceGuard destructor and Release(). Dispatches + /// a release request to the daemon. Always callable as long as the + /// guard is active. + /// + /// @param id Handle of the transient resource to release + /// @return std::monostate on success; error if the resource is invalid or still + /// referenced by an active operation context + virtual score::Result ReleaseResource(const CryptoResourceId& id) noexcept = 0; + + protected: + IReleaseCallback() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_COMMON_SRC_I_RELEASE_CALLBACK_HPP diff --git a/score/mw/crypto/api/common/types.hpp b/score/mw/crypto/api/common/types.hpp new file mode 100644 index 0000000..88e3764 --- /dev/null +++ b/score/mw/crypto/api/common/types.hpp @@ -0,0 +1,442 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_COMMON_TYPES_HPP +#define SCORE_MW_CRYPTO_API_COMMON_TYPES_HPP + +#include "score/mw/crypto/api/common/error_domain.hpp" +#include "score/mw/crypto/api/common/fixed_capacity_string.hpp" + +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Application-defined name for config files (e.g., "KeySlot_42"). +/// Only used at resolution time via ICryptoContext::ResolveResource(). +/// +/// Uses FixedCapacityString<64> — stack-allocated, no heap allocation. +/// Resource names are deployment-time configuration values (bounded, immutable). +/// All observed resource name strings are well under 64 characters. +using ResourceId = FixedCapacityString<64>; + +/// @brief String-based algorithm identifier for extensibility (including PQC). +/// +/// Uses FixedCapacityString<64> — a stack-allocated, non-heap-allocating string +/// that satisfies automotive safety requirements (deterministic memory, no heap +/// fragmentation) while preserving extensibility. New algorithms can be added +/// at the daemon level without modifying the client library — any algorithm name +/// up to 64 characters is accepted at runtime. +/// +/// Examples: "AES-256-CBC", "SHA-256", "ECDSA-P256", "ML-KEM-768", "ML-DSA-65", +/// "SLH-DSA-SHA2-128s", "XMSS-SHA2_10_256" +/// +/// Implicit conversion to std::string_view enables zero-copy interop. +/// Explicit conversion to std::string available for IPC serialization. +using AlgorithmId = FixedCapacityString<64>; + +/// @brief Type of crypto resource managed by the daemon. +/// +/// kKey and kCertificate identify live objects (key material, parsed certs); +/// kKeySlot and kCertSlot identify only persistent storage locations. +enum class ResourceType : uint8_t +{ + kProvider, ///< Crypto provider / device + kKeySlot, ///< Persistent key storage slot + kCertSlot, ///< Persistent certificate storage slot + kVerificationTrustStore, ///< Named group of trusted CA certificates used for certificate chain + ///< verification. + kKey, ///< Key material (generated / loaded / derived / imported) + kCertificate, ///< Parsed or stored certificate object + kCrl, ///< Certificate Revocation List — shares the same numeric id + ///< as the issuer certificate resource (differentiated by type field) + kSecureObject, ///< Secure storage entry + kDataObject ///< Generic data blob +}; + +/// @brief Persistence classification of a crypto resource. +enum class ResourcePersistence : uint8_t +{ + kPersistent, ///< Survives context/stack destruction, stored in provider + kEphemeral ///< Auto-cleaned on context/stack destruction +}; + +/// @brief The sole runtime handle for all resolved crypto resources. +/// +/// Applications resolve string-based ResourceId to CryptoResourceId once via +/// ICryptoContext::ResolveResource(), then use this compact struct for all +/// subsequent operations. No string fields — all comparisons are numeric/enum. +/// +/// @note Struct is ~16 bytes with padding, fully numeric, cheap to copy and hash. +struct CryptoResourceId +{ + uint64_t id{0U}; ///< Daemon-assigned, unique per session + ResourceType type{ResourceType::kKeySlot}; ///< Resource classification + ResourcePersistence persistence{ResourcePersistence::kEphemeral}; ///< Lifetime + uint16_t primary_provider{0U}; ///< Daemon-assigned numeric provider index. + ///< Embeds device binding: identifies which + ///< provider/device owns this resource. + ///< 0 = unbound (e.g., trust anchors). + + constexpr bool operator==(const CryptoResourceId& other) const noexcept + { + return (id == other.id) && (type == other.type) && (persistence == other.persistence) && + (primary_provider == other.primary_provider); + } + + constexpr bool operator!=(const CryptoResourceId& other) const noexcept + { + return !(*this == other); + } +}; + +/// @brief Preference for selecting a crypto provider when not explicitly specified. +enum class ProviderType : uint8_t +{ + kDefault, ///< Daemon selects the most appropriate provider + kHardware, ///< Require a hardware provider (HSM/TEE) + kSoftware, ///< Require a software provider (OpenSSL/wolfSSL) + kHardwarePreferred, ///< Prefer hardware, fall back to software + kSoftwarePreferred ///< Prefer software, fall back to hardware +}; + +/// @brief Certificate and key data encoding format. +enum class FormatType : uint8_t +{ + kDer, ///< DER (binary) encoding + kPem ///< PEM (base64-armored) encoding +}; + +/// @brief State of a key slot. +/// +/// Renamed from KeySlotStatus to disambiguate from CertificateStatus. +enum class KeySlotState : uint8_t +{ + kEmpty, ///< Slot contains no key material + kOccupied, ///< Slot contains a key + kLocked ///< Slot is in use and cannot be modified +}; + +/// @brief Validity status of a certificate. +enum class CertificateStatus : uint8_t +{ + kValid, ///< Certificate is valid + kRevoked, ///< Certificate has been revoked + kExpired, ///< Certificate has expired + kUnknown ///< Status cannot be determined +}; + +/// @brief Direction for symmetric cipher and AEAD operations. +enum class CipherDirection : uint8_t +{ + kEncrypt, ///< Encryption / sealing direction + kDecrypt ///< Decryption / opening direction +}; + +/// @brief Intended usage mode for MAC and signature contexts. +/// +/// Specifies whether the context will be used to generate (sign) or verify. +/// Used for: +/// - Key permission enforcement (kSign for generation, kVerify for verification). +/// - Provider-specific API selection (e.g. PKCS#11 C_SignInit vs C_VerifyInit). +/// +/// @note CipherDirection covers encrypt/decrypt for symmetric ciphers. +/// OperationMode covers MAC generation/verification and future signature contexts. +enum class OperationMode : uint8_t +{ + kGenerate, ///< MAC generation / signature creation + kVerify ///< MAC verification / signature verification +}; + +/// @brief Type of shared memory to allocate for the data plane. +enum class MemoryType : uint8_t +{ + kDefault, ///< Daemon-managed shared memory, suitable for most providers. + ///< The daemon may copy data into provider-compatible memory internally. + kProviderCompatible ///< Memory directly usable by a specific provider (e.g., DMA-capable + ///< for HW/TEE), enabling true zero-copy from application through + ///< daemon to the crypto device. +}; + +/// @brief Controls the revocation checking strategy in ICertificateVerificationContext. +enum class RevocationCheckPolicy : uint8_t +{ + kNone, ///< No revocation checking + kCrlOnly, ///< Check revocation using CRL only + kOcspOnly, ///< Check revocation using OCSP only + kOcspWithCrlFallback ///< Prefer OCSP, fall back to CRL if OCSP is unavailable +}; + +/// @brief Bitmask defining which cryptographic operations a key is permitted to perform. +/// +/// Key operation permissions enforce the principle of least privilege: a key +/// configured for signing cannot be misused for encryption, and vice versa. +/// Permissions are assigned when a key slot is provisioned (daemon-side +/// configuration) and optionally constrained further at key generation time. +/// +/// The permission model uses a capability-centric bitmask grouped by +/// operation category: +/// - **Data protection** (bits 0–3): encrypt, decrypt, wrap, unwrap +/// - **Authentication** (bits 4–7): sign, verify, mac, agree +/// - **Key lifecycle** (bits 8–10): derive, export, import +/// +/// Composite presets are provided for common deployment patterns. +/// Use bitwise OR to combine individual permissions. +/// +/// @note Permission enforcement is performed by the daemon at context +/// creation time. If a key's permissions do not include the operation +/// requested by the context, the daemon returns +/// CryptoErrorCode::kKeyOperationNotPermitted. +enum class KeyOperationPermission : uint32_t +{ + kNone = 0x0000U, ///< No operations permitted (storage-only key) + + // ---- Data protection (bits 0–3) ---- + kEncrypt = 0x0001U, ///< Symmetric/asymmetric encryption + kDecrypt = 0x0002U, ///< Symmetric/asymmetric decryption + kWrap = 0x0004U, ///< Key wrapping (encrypting another key) + kUnwrap = 0x0008U, ///< Key unwrapping (decrypting a wrapped key) + + // ---- Authentication (bits 4–7) ---- + kSign = 0x0010U, ///< Digital signature generation + kVerify = 0x0020U, ///< Digital signature verification + kMac = 0x0040U, ///< MAC generation and verification + kAgree = 0x0080U, ///< Key agreement (ECDH, ML-KEM decapsulation) + + // ---- Key lifecycle (bits 8–10) ---- + kDerive = 0x0100U, ///< Key derivation (as source key) + kExport = 0x0200U, ///< Export raw key material (for exportable keys) + kImport = 0x0400U, ///< Slot accepts imported key material + + // ---- Composite presets (common deployment patterns) ---- + + /// Data protection: encrypt + decrypt + wrap + unwrap + kDataProtection = 0x000FU, + /// Authentication: sign + verify + mac + agree + kAuthentication = 0x00F0U, + /// Full lifecycle: derive + export + import + kFullLifecycle = 0x0700U, + /// All operations permitted (no restrictions) + kAll = 0x07FFU, +}; + +/// @brief Bitwise OR for combining key operation permissions. +inline constexpr KeyOperationPermission operator|(KeyOperationPermission lhs, KeyOperationPermission rhs) noexcept +{ + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +/// @brief Bitwise AND for testing key operation permissions. +inline constexpr KeyOperationPermission operator&(KeyOperationPermission lhs, KeyOperationPermission rhs) noexcept +{ + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + +/// @brief Bitwise NOT for inverting key operation permissions. +/// @note Result is masked to valid permission bits (0–10) to prevent undefined states. +inline constexpr KeyOperationPermission operator~(KeyOperationPermission perm) noexcept +{ + constexpr uint32_t kValidBitsMask = 0x07FFU; // Bits 0–10 only + return static_cast((~static_cast(perm)) & kValidBitsMask); +} + +/// @brief Bitwise OR-assign for accumulating key operation permissions. +inline constexpr KeyOperationPermission& operator|=(KeyOperationPermission& lhs, KeyOperationPermission rhs) noexcept +{ + lhs = lhs | rhs; + return lhs; +} + +/// @brief Bitwise AND-assign for masking key operation permissions. +inline constexpr KeyOperationPermission& operator&=(KeyOperationPermission& lhs, KeyOperationPermission rhs) noexcept +{ + lhs = lhs & rhs; + return lhs; +} + +/// @brief Tests whether a permission set includes a specific required permission. +/// @param granted The permission set to test (e.g., from KeySlotInfo) +/// @param required The permission(s) being checked +/// @return true if all bits in required are set in granted +inline constexpr bool HasPermission(KeyOperationPermission granted, KeyOperationPermission required) noexcept +{ + // Only consider defined permission bits (bits 0..10). Mask both operands + // to avoid granting permissions due to out-of-range/invalid bits. + constexpr uint32_t kValidBitsMask = 0x07FFU; // bits 0..10 + const uint32_t g = static_cast(granted) & kValidBitsMask; + const uint32_t r = static_cast(required) & kValidBitsMask; + return (g & r) == r; +} + +/// @brief Information about a certificate slot and its contents. +/// +/// Returned by ICertificateManagementContext::GetCertificateSlotInfo(). +struct CertificateSlotInfo +{ + bool occupied{false}; ///< Whether the slot contains a certificate + AlgorithmId algorithm{}; ///< Public key algorithm of the stored certificate (empty if unoccupied) + uint16_t primary_provider{0U}; ///< Provider/device that owns this slot +}; + +/// @brief Information about a key slot and its contents. +/// +/// Returned by IKeyManagementContext::GetKeySlotInfo(). Exposes device binding, +/// cross-provider compatibility, and permitted operations so applications can +/// make informed decisions. +struct KeySlotInfo +{ + KeySlotState state{KeySlotState::kEmpty}; ///< State of the key slot + AlgorithmId algorithm{}; ///< Algorithm of the stored key (empty if slot is empty) + uint16_t primary_provider{0U}; ///< Provider/device that owns this slot + + /// @brief Secondary providers that can also use keys in this slot. + /// + /// Fixed-capacity array (max 8 providers). Use compatible_provider_count + /// to determine how many entries are valid. + static constexpr std::size_t kMaxCompatibleProviders = 8U; + std::array compatible_providers{}; + std::size_t compatible_provider_count{0U}; + + /// @brief Operations this key slot permits. + /// + /// Defaults to kAll for backward compatibility. When provisioned with + /// restricted permissions, the daemon enforces them at context creation + /// time — creating an encrypt context with a sign-only key returns + /// CryptoErrorCode::kKeyOperationNotPermitted. + KeyOperationPermission permitted_operations{KeyOperationPermission::kAll}; +}; + +/// @brief Human-readable provider metadata. +/// +/// Returned by ICryptoContext::GetProviderInfo(). Maps daemon-assigned numeric +/// provider IDs to descriptive information. +struct ProviderInfo +{ + uint16_t id{0U}; ///< Daemon-assigned provider index + ProviderType type{ProviderType::kDefault}; ///< Provider classification + FixedCapacityString<32> name{}; ///< Human-readable provider name (e.g., "OpenSSL", "SoftHSM") +}; + +/// @brief Cross-provider compatibility information for a resource. +/// +/// Returned by ICryptoContext::QueryProviderCompatibility(). Secondary providers +/// are those that can also use this resource (e.g., a SW-exported key re-importable +/// into another SW provider). Not embedded in CryptoResourceId because the +/// secondary list is variable-length and mutable daemon-side state. +struct ProviderCompatibilityInfo +{ + CryptoResourceId resource{}; ///< The queried resource + uint16_t primary_provider{0U}; ///< Owning provider + + /// @brief Providers that can also use this resource. + /// + /// Fixed-capacity array (max 8 providers). Use secondary_provider_count + /// to determine how many entries are valid. + static constexpr std::size_t kMaxSecondaryProviders = 8U; + std::array secondary_providers{}; + std::size_t secondary_provider_count{0U}; +}; + +/// @brief Capabilities of a specific algorithm as reported by the daemon. +/// +/// Returned by ICryptoContext::QueryCapabilities(). Includes PQC algorithms +/// (e.g., "ML-KEM-768", "ML-DSA-65") when supported by configured providers. +struct AlgorithmCapabilities +{ + AlgorithmId id{}; ///< Algorithm identifier + bool supported{false}; ///< Whether any configured provider supports this algorithm + + /// @brief Supported modes/variants (e.g., "CBC", "GCM", "CTR"). + /// + /// Fixed-capacity array (max 16 modes). Use mode_count to determine + /// how many entries are valid. + static constexpr std::size_t kMaxModes = 16U; + std::array, kMaxModes> modes{}; + std::size_t mode_count{0U}; +}; + +/// @brief Aggregate view of all providers and supported algorithms. +/// +/// Returned by the parameterless ICryptoContext::QueryCapabilities() overload. +/// Provides a complete snapshot of the system's crypto capabilities. +struct SystemCapabilities +{ + /// @brief All configured providers. + /// + /// Fixed-capacity array (max 16 providers). Use provider_count to determine + /// how many entries are valid. + static constexpr std::size_t kMaxProviders = 16U; + std::array providers{}; + std::size_t provider_count{0U}; + + /// @brief All supported algorithms. + /// + /// Fixed-capacity array (max 64 algorithms). Use algorithm_count to determine + /// how many entries are valid. + static constexpr std::size_t kMaxAlgorithms = 64U; + std::array algorithms{}; + std::size_t algorithm_count{0U}; +}; + +/// @brief Single key-value entry for algorithm- or operation-scoped extended parameters. +struct ExtendedParameterEntry +{ + FixedCapacityString<32> key{}; ///< Parameter name (middleware-defined, not provider-defined) + FixedCapacityString<64> value{}; ///< Parameter value +}; + +/// @brief Fixed-capacity key-value map for algorithm- or operation-scoped extended parameters. +/// +/// Provides a forward-compatible extension point in context configs for parameters +/// that are not yet modeled as typed fields (e.g., PQC parameter sets, key-derivation +/// context strings). Keys and their semantics are defined by the **middleware +/// specification** — never by the underlying crypto provider or hardware back-end. +/// +/// Provider-specific tuning (HSM slot indices, PIN policies, vendor flags, etc.) +/// belongs exclusively in the daemon's static configuration and must NOT appear +/// here. Application code using this struct must remain portable across all +/// compliant provider implementations. +/// +/// Max 16 entries. +struct ExtendedParameters +{ + static constexpr std::size_t kMaxEntries = 16U; + std::array entries{}; + std::size_t entry_count{0U}; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +/// @brief std::hash specialization for CryptoResourceId, enabling use in unordered containers. +template <> +struct std::hash +{ + std::size_t operator()(const score::mw::crypto::CryptoResourceId& rid) const noexcept + { + std::size_t h = std::hash{}(rid.id); + h ^= std::hash{}(static_cast(rid.type)) << 1U; + h ^= std::hash{}(static_cast(rid.persistence)) << 2U; + h ^= std::hash{}(rid.primary_provider) << 3U; + return h; + } +}; + +#endif // SCORE_MW_CRYPTO_API_COMMON_TYPES_HPP diff --git a/score/mw/crypto/api/config/BUILD b/score/mw/crypto/api/config/BUILD new file mode 100644 index 0000000..c9637e2 --- /dev/null +++ b/score/mw/crypto/api/config/BUILD @@ -0,0 +1,30 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +#Copyright(c) 2026 by ETAS GmbH.All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "context_configs", + hdrs = [ + "base_context_config.hpp", + "hash_context_config.hpp", + "key_management_context_config.hpp", + "key_operation_params.hpp", + "mac_context_config.hpp", + "permission_builder.hpp", + ], + includes = ["."], + visibility = ["//visibility:public"], + deps = [ + "//score/mw/crypto/api/common:crypto_common", + ], +) diff --git a/score/mw/crypto/api/config/base_context_config.hpp b/score/mw/crypto/api/config/base_context_config.hpp new file mode 100644 index 0000000..3a9ef84 --- /dev/null +++ b/score/mw/crypto/api/config/base_context_config.hpp @@ -0,0 +1,192 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_BASE_CONTEXT_CONFIG_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_BASE_CONTEXT_CONFIG_HPP + +#include "score/mw/crypto/api/common/types.hpp" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Base configuration for all operation contexts. +/// +/// **Provider auto-resolution**: When provider is omitted but a key_slot +/// is specified in a derived config, the daemon auto-resolves the provider +/// from CryptoResourceId::primary_provider of the key slot. Explicitly +/// setting provider overrides this — enabling cross-provider scenarios +/// where the app wants to use a secondary-compatible provider instead +/// of the key's primary. +struct BaseContextConfig +{ + /// @brief Algorithm identifier (e.g., "AES-256-CBC", "SHA-384", "ML-DSA-65"). + AlgorithmId algorithm{}; + + /// @brief Optional resolved provider handle. When set, overrides + /// auto-resolution from key_slot's primary_provider. + std::optional provider{std::nullopt}; + + /// @brief Optional provider type preference (e.g., kHardwarePreferred). + /// Used when provider handle is not set to guide daemon selection. + std::optional provider_type{std::nullopt}; + + /// @brief Algorithm- or operation-scoped extended parameters. + /// + /// Forward-compatible extension point for parameters not yet modeled as typed + /// fields (e.g., PQC parameter sets, key-derivation labels). Key names and + /// value semantics are defined by the **middleware specification** only — + /// never by an individual provider or hardware back-end. Application code + /// using this field must remain portable across all compliant providers. + /// + /// Provider-specific or hardware-specific tuning (HSM slot indices, PIN + /// policies, vendor flags, etc.) must NOT be placed here; it belongs in + /// the daemon's static configuration. + ExtendedParameters extended_parameters{}; + + /// @brief Per-context operation timeout override. + /// + /// When set (and timeout_enabled is true), overrides the stack-level + /// default_operation_timeout for this context's IPC calls. + /// When std::nullopt, falls back to CryptoStackConfig::default_operation_timeout. + /// + /// @note Applies per-IPC-call, not per-sequence. For streaming operations, + /// each Init()/Update()/Finalize() has its own deadline. + std::optional operation_timeout{std::nullopt}; + + /// @brief Controls whether timeout enforcement is active for this context. + /// + /// When true (default), the effective timeout (per-context or stack-level) + /// is enforced on every IPC call. Operations exceeding the deadline return + /// CryptoErrorCode::kOperationTimedOut and the context transitions to an + /// error state — subsequent calls return kInvalidOperation. + /// + /// When false, timeout is disabled entirely for this context, allowing + /// unbounded execution. Use for operations that are legitimately + /// long-running, such as: + /// - PQC key generation on hardware (ML-KEM, ML-DSA) + /// - HSM-backed key agreement or unwrapping + /// - Certificate chain verification with online OCSP + /// + /// @warning Disabling timeout removes the WCET bound for this context's + /// operations, which may impact Safety. Document the + /// rationale in the safety case when using DisableTimeout() in + /// safety-relevant applications. + bool timeout_enabled{true}; + + /// @brief Intended operation mode for MAC and signature contexts. + /// + /// Defaults to kGenerate. Set to kVerify for verification-only contexts + /// so that key permission enforcement and provider API selection (e.g. + /// PKCS#11 C_VerifyInit vs C_SignInit) are correct. + OperationMode operation_mode{OperationMode::kGenerate}; + + // -- Fluent builder -- + + BaseContextConfig& SetAlgorithm(const AlgorithmId& alg) noexcept + { + algorithm = alg; + return *this; + } + + BaseContextConfig& SetProvider(const CryptoResourceId& prov) noexcept + { + provider = prov; + return *this; + } + + BaseContextConfig& SetProviderType(ProviderType type) noexcept + { + provider_type = type; + return *this; + } + + BaseContextConfig& SetOperationMode(OperationMode mode) noexcept + { + operation_mode = mode; + return *this; + } + + /// @brief Add or update a small, portable extended parameter. + /// + /// Keys must follow the middleware API naming rules; values are opaque + /// strings. This method overwrites an existing key or appends a new entry + /// if capacity allows. + BaseContextConfig& SetExtendedParameter(const std::string& key, const std::string& value) + { + // Update existing entry if key is already present + for (std::size_t i = 0U; i < extended_parameters.entry_count; ++i) + { + if (extended_parameters.entries[i].key == key) + { + extended_parameters.entries[i].value = value; + return *this; + } + } + // Add new entry if capacity allows + if (extended_parameters.entry_count < extended_parameters.entries.size()) + { + extended_parameters.entries[extended_parameters.entry_count].key = key; + extended_parameters.entries[extended_parameters.entry_count].value = value; + ++extended_parameters.entry_count; + } + return *this; + } + + // TODO: 0 means infinite duration? (or time out disabled?). Add sanity check. + /// @brief Sets the per-context operation timeout. + /// @param timeout Maximum duration for each individual IPC call. + /// Overrides CryptoStackConfig::default_operation_timeout. + /// Automatically enables timeout if it was previously disabled. + BaseContextConfig& SetOperationTimeout(std::chrono::milliseconds timeout) + { + operation_timeout = timeout; + timeout_enabled = true; + return *this; + } + + /// @brief Disables timeout enforcement for this context. + /// + /// The context will wait indefinitely for daemon responses. Use for + /// operations that are legitimately long-running (e.g., PQC key + /// generation on hardware tokens). + /// + /// @warning Removes WCET bound — document rationale in safety case. + BaseContextConfig& DisableTimeout() noexcept + { + timeout_enabled = false; + return *this; + } + + /// @brief Re-enables timeout enforcement for this context. + /// + /// Uses the per-context operation_timeout if set, otherwise falls back + /// to CryptoStackConfig::default_operation_timeout. + BaseContextConfig& EnableTimeout() noexcept + { + timeout_enabled = true; + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_BASE_CONTEXT_CONFIG_HPP diff --git a/score/mw/crypto/api/config/hash_context_config.hpp b/score/mw/crypto/api/config/hash_context_config.hpp new file mode 100644 index 0000000..372f3b3 --- /dev/null +++ b/score/mw/crypto/api/config/hash_context_config.hpp @@ -0,0 +1,68 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_HASH_CONTEXT_CONFIG_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_HASH_CONTEXT_CONFIG_HPP + +#include "score/mw/crypto/api/config/base_context_config.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Configuration for hash context creation. +/// +/// Requires only an algorithm (e.g., "SHA-256", "SHA-384", "SHA3-256", "SHAKE-256"). +/// No key slot is needed for hash operations. +/// +/// @par Example +/// @code +/// HashContextConfig config; +/// config.SetAlgorithm("SHA-256"); +/// auto ctx = crypto_context->CreateHashContext(config); +/// @endcode +struct HashContextConfig : public BaseContextConfig +{ + + HashContextConfig& SetAlgorithm(const AlgorithmId& alg) noexcept + { + BaseContextConfig::SetAlgorithm(alg); + return *this; + } + + HashContextConfig& SetProvider(const CryptoResourceId& prov) noexcept + { + BaseContextConfig::SetProvider(prov); + return *this; + } + + HashContextConfig& SetProviderType(ProviderType type) noexcept + { + BaseContextConfig::SetProviderType(type); + return *this; + } + + HashContextConfig& SetExtendedParameter(const std::string& key, const std::string& value) + { + BaseContextConfig::SetExtendedParameter(key, value); + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_HASH_CONTEXT_CONFIG_HPP diff --git a/score/mw/crypto/api/config/key_management_context_config.hpp b/score/mw/crypto/api/config/key_management_context_config.hpp new file mode 100644 index 0000000..b5a9e10 --- /dev/null +++ b/score/mw/crypto/api/config/key_management_context_config.hpp @@ -0,0 +1,65 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_KEY_MANAGEMENT_CONTEXT_CONFIG_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_KEY_MANAGEMENT_CONTEXT_CONFIG_HPP + +#include "score/mw/crypto/api/config/base_context_config.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Configuration for key management context creation. +/// +/// Provider is optional — operations will use the provider associated +/// with each key's CryptoResourceId::primary_provider as needed. +/// Setting a provider explicitly scopes all key management operations +/// to that specific provider. +/// +/// @par Example +/// @code +/// KeyManagementContextConfig config; +/// config.SetProviderType(ProviderType::kHardware); +/// auto ctx = crypto_context->CreateKeyManagementContext(config); +/// @endcode +struct KeyManagementContextConfig : public BaseContextConfig +{ + // -- Fluent builder -- + + KeyManagementContextConfig& SetProvider(const CryptoResourceId& prov) noexcept + { + BaseContextConfig::SetProvider(prov); + return *this; + } + + KeyManagementContextConfig& SetProviderType(ProviderType type) noexcept + { + BaseContextConfig::SetProviderType(type); + return *this; + } + + KeyManagementContextConfig& SetExtendedParameter(const std::string& key, const std::string& value) + { + BaseContextConfig::SetExtendedParameter(key, value); + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_KEY_MANAGEMENT_CONTEXT_CONFIG_HPP diff --git a/score/mw/crypto/api/config/key_operation_params.hpp b/score/mw/crypto/api/config/key_operation_params.hpp new file mode 100644 index 0000000..185441f --- /dev/null +++ b/score/mw/crypto/api/config/key_operation_params.hpp @@ -0,0 +1,585 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_KEY_OPERATION_PARAMS_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_KEY_OPERATION_PARAMS_HPP + +#include "score/mw/crypto/api/common/types.hpp" +#include "score/span.hpp" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +// TODO: Check enforcing the builder pattern for config creation. + +/// @brief Structured parameters for key derivation functions. +/// +// clang-format off +/// | KDF | kdf_algorithm | salt | label | seed | output_key_length | iteration_count | +/// |-----------------------|-----------------------|------------------|-------------------------------|-------------------------------|-------------------|-----------------| +/// | HKDF (RFC 5869) | "HKDF-SHA256" | Extract salt | Expand info | — | Required | — | +/// | TLS 1.2 PRF | "TLS12-PRF-SHA256" | — | "master secret" | client_random ‖ server_random | Required | — | +/// | | | | "key expansion" | | | | +/// | TLS 1.2 EMS | "TLS12-PRF-SHA256" | — | "extended master secret" | transcript hash | Required | — | +/// | TLS 1.3 HKDF | "TLS13-HKDF-SHA256" | — | "derived" / "c hs traffic" | transcript hash | Required | — | +/// | PBKDF2 (RFC 8018) | "PBKDF2-SHA256" | Password salt | — | — | Required | Required | +/// | SP800-108 CTR-HMAC | "SP800-108-CTR-HMAC" | — | Label | Context | Required | — | +// clang-format on +/// +/// All fields use fixed-capacity types — zero heap allocation. +/// +/// @note Validation: Validate() is called automatically by DeriveKeyParams::Validate() +/// and AgreeKeyParams::Validate(), which implementations invoke before IPC dispatch. +struct KdfParameters +{ + /// @brief KDF algorithm identifier (required). + /// Examples: "HKDF-SHA256", "TLS12-PRF-SHA256", "TLS13-HKDF-SHA256", + /// "PBKDF2-SHA256", "SP800-108-CTR-HMAC" + AlgorithmId kdf_algorithm{}; + + /// @brief Salt for HKDF-Extract or PBKDF2. Unused for TLS PRF. + static constexpr std::size_t kMaxSaltLength = 128U; + std::array salt{}; + std::size_t salt_length{0U}; + + /// @brief Label / info string for HKDF-Expand, TLS PRF label, or SP800-108 Label. + /// Examples: "master secret", "extended master secret", "key expansion", + /// "derived", "c hs traffic", "s hs traffic", "c ap traffic" + FixedCapacityString<128> label{}; + + /// @brief Seed / context bytes for TLS PRF seed (client_random || server_random), + /// TLS 1.3 transcript hash, or SP800-108 Context. + static constexpr std::size_t kMaxSeedLength = 256U; + std::array seed{}; + std::size_t seed_length{0U}; + + /// @brief Desired output key length in bytes. + /// Required for all KDFs except when the algorithm implies a fixed output size. + std::optional output_key_length{std::nullopt}; + + /// @brief Iteration count for PBKDF2. Ignored by other KDFs. + std::optional iteration_count{std::nullopt}; + + // -- Fluent builder -- + // TODO: Check enforcing the builder pattern for config creation. + + KdfParameters& SetKdfAlgorithm(const AlgorithmId& alg) noexcept + { + kdf_algorithm = alg; + return *this; + } + + KdfParameters& SetSalt(const uint8_t* data, std::size_t len) noexcept + { + if (len > kMaxSaltLength) + { + truncated_ = true; + salt_length = kMaxSaltLength; + } + else + { + salt_length = len; + } + for (std::size_t i = 0U; i < salt_length; ++i) + { + salt[i] = data[i]; + } + return *this; + } + + KdfParameters& SetLabel(const char* lbl) noexcept + { + label = FixedCapacityString<128>(lbl); + if (label.truncated()) + { + truncated_ = true; + } + return *this; + } + + KdfParameters& SetSeed(const uint8_t* data, std::size_t len) noexcept + { + if (len > kMaxSeedLength) + { + truncated_ = true; + seed_length = kMaxSeedLength; + } + else + { + seed_length = len; + } + for (std::size_t i = 0U; i < seed_length; ++i) + { + seed[i] = data[i]; + } + return *this; + } + + KdfParameters& SetOutputKeyLength(uint32_t len) noexcept + { + output_key_length = len; + return *this; + } + + KdfParameters& SetIterationCount(uint32_t count) noexcept + { + iteration_count = count; + return *this; + } + + /// @brief Validates the constructed parameters for consistency and truncation. + /// + /// Checks: + /// - No silent truncation occurred in SetSalt() or SetSeed() — returns + /// kParamTruncated. This is the only place this error can be caught: + /// the daemon receives a clean fixed-size array and cannot detect truncation. + /// - KDF algorithm is specified (non-empty) — returns kInvalidArgument. + /// This is an advisory early-fail check; the daemon would also reject it. + /// + /// @note Calling Validate() before DeriveKey() / AgreeKey() is strongly + /// recommended for the truncation check. + score::Result Validate() const noexcept + { + if (truncated_) + { + return score::Result{score::unexpect, MakeError(CryptoErrorCode::kParamTruncated)}; + } + if (kdf_algorithm.empty()) + { + return score::Result{score::unexpect, MakeError(CryptoErrorCode::kInvalidArgument)}; + } + return score::Result{std::monostate{}}; + } + + private: + /// @brief Tracks if any Set*() call silently truncated input (salt or seed too long). + bool truncated_{false}; +}; + +/// @brief Parameters for key generation via IKeyManagementContext::GenerateKey(). +/// +/// Supports both symmetric (AES) and asymmetric (RSA, ECDH, ML-DSA, ML-KEM) algorithms. +/// For asymmetric algorithms, the public key is derived from the private key. +/// Public key permissions and export flags are only meaningful for asymmetric generation. +struct GenerateKeyParams +{ + /// @brief Algorithm identifier for the key to generate (required). + /// Examples: "AES-256", "RSA-2048", "ECDH-P256", "ML-KEM-768", "ML-DSA-65" + AlgorithmId algorithm{}; + + /// @brief Operations the generated key is permitted to perform. + /// For symmetric keys: controls encrypt/decrypt/wrap/unwrap permissions. + /// For asymmetric keys: controls permissions of the private key (sign/decrypt/agree). + /// Defaults to kAll (no restrictions). The daemon enforces permissions + /// at context creation time. + KeyOperationPermission permissions{KeyOperationPermission::kAll}; + + /// @brief Operations the public key is permitted to perform (asymmetric only). + /// When set (not std::nullopt), controls public key usage (verify/encrypt). + /// Use kExport bit to control public key exportability. + /// When omitted (default std::nullopt), public key inherits full permissions. + /// Only meaningful when algorithm is asymmetric. + std::optional public_key_permissions{std::nullopt}; + + // -- Fluent builder -- + + GenerateKeyParams& SetAlgorithm(const AlgorithmId& alg) noexcept + { + algorithm = alg; + return *this; + } + + GenerateKeyParams& SetPermissions(KeyOperationPermission perms) noexcept + { + permissions = perms; + return *this; + } + + GenerateKeyParams& SetPublicKeyPermissions(KeyOperationPermission perms) noexcept + { + public_key_permissions = perms; + return *this; + } +}; + +/// @brief Parameters for key derivation via IKeyManagementContext::DeriveKey(). +/// +/// Supports HKDF, TLS 1.2 PRF, TLS 1.3 HKDF, PBKDF2, and SP800-108 +/// via the structured KdfParameters field. +struct DeriveKeyParams +{ + /// @brief Handle to the source key (required). CryptoResourceId with type = kKey. + CryptoResourceId source_key{}; + + /// @brief Algorithm identifier for the derived key (required). + /// Determines the type and length of the output key. + AlgorithmId derived_key_algorithm{}; + + /// @brief KDF parameters: algorithm, salt, label, seed, output length (required). + KdfParameters kdf{}; + + /// @brief Operations the derived key is permitted to perform. + KeyOperationPermission permissions{KeyOperationPermission::kAll}; + + // -- Fluent builder -- + + DeriveKeyParams& SetSourceKey(const CryptoResourceId& key) noexcept + { + source_key = key; + return *this; + } + + DeriveKeyParams& SetDerivedKeyAlgorithm(const AlgorithmId& alg) noexcept + { + derived_key_algorithm = alg; + return *this; + } + + DeriveKeyParams& SetKdf(const KdfParameters& params) noexcept + { + kdf = params; + return *this; + } + + DeriveKeyParams& SetPermissions(KeyOperationPermission perms) noexcept + { + permissions = perms; + return *this; + } + + /// @brief Validates these parameters. + /// + /// Propagates KdfParameters::Validate() — in particular, catches truncation + /// that occurred in SetSalt() or SetSeed(). + score::Result Validate() const noexcept + { + return kdf.Validate(); + } +}; + +/// @brief Parameters for key agreement via IKeyManagementContext::AgreeKey(). +/// +/// Supports raw key agreement (ECDH, X25519, ML-KEM decapsulation) and +/// combined agree-then-derive (ECIES, TLS key exchange) when kdf is set. +struct AgreeKeyParams +{ + /// @brief Handle to this party's private key (required). + CryptoResourceId private_key{}; + + /// @brief The other party's public key data (required). + /// Points to caller-owned memory — must remain valid until the call returns. + score::cpp::span peer_public_key{}; + + /// @brief Key agreement algorithm (required). + /// Examples: "ECDH-P256", "X25519", "ML-KEM-768" + AlgorithmId agreement_algorithm{}; + + /// @brief Format of the peer public key data. Defaults to raw/uncompressed. + std::optional public_key_format{std::nullopt}; + + /// @brief Algorithm for the output key when KDF is used (optional). + /// When set together with kdf, the daemon shall perform agreement + KDF atomically. + /// When omitted (and kdf is omitted), the raw shared secret is returned. + std::optional derived_key_algorithm{std::nullopt}; + + /// @brief KDF parameters for combined agree-then-derive (optional). + /// When set, the raw shared secret is fed through the KDF before being + /// returned as a key of type derived_key_algorithm. + std::optional kdf{std::nullopt}; + + /// @brief Operations the agreed/derived key is permitted to perform. + KeyOperationPermission permissions{KeyOperationPermission::kAll}; + + // -- Fluent builder -- + + AgreeKeyParams& SetPrivateKey(const CryptoResourceId& key) noexcept + { + private_key = key; + return *this; + } + + AgreeKeyParams& SetPeerPublicKey(score::cpp::span data) noexcept + { + peer_public_key = data; + return *this; + } + + AgreeKeyParams& SetAgreementAlgorithm(const AlgorithmId& alg) noexcept + { + agreement_algorithm = alg; + return *this; + } + + AgreeKeyParams& SetPublicKeyFormat(FormatType fmt) noexcept + { + public_key_format = fmt; + return *this; + } + + AgreeKeyParams& SetDerivedKeyAlgorithm(const AlgorithmId& alg) noexcept + { + derived_key_algorithm = alg; + return *this; + } + + AgreeKeyParams& SetKdf(const KdfParameters& params) noexcept + { + kdf = params; + return *this; + } + + AgreeKeyParams& SetPermissions(KeyOperationPermission perms) noexcept + { + permissions = perms; + return *this; + } + + /// @brief Validates these parameters before dispatch to the daemon. + /// + /// When kdf is set, propagates KdfParameters::Validate() to catch truncation + /// that occurred in SetSalt() or SetSeed(). + score::Result Validate() const noexcept + { + if (kdf.has_value()) + { + return kdf->Validate(); + } + return score::Result{std::monostate{}}; + } +}; + +/// @brief Parameters for key wrapping via IKeyManagementContext::WrapKey(). +struct WrapKeyParams +{ + /// @brief Handle to the key to wrap (required). + CryptoResourceId key_to_wrap{}; + + /// @brief Handle to the wrapping key / KEK (required). + CryptoResourceId wrapping_key{}; + + /// @brief Wrapping algorithm (optional). When omitted, the daemon selects + /// based on the wrapping key's algorithm (e.g., AES-KEYWRAP for AES keys). + std::optional wrapping_algorithm{std::nullopt}; + + /// @brief Initialization vector for wrapping algorithms that require one + /// (e.g., AES-GCM, AES-CBC). Points to caller-owned memory. + score::cpp::span iv{}; + + /// @brief Additional authenticated data for AEAD wrapping algorithms + /// (e.g., AES-GCM). Points to caller-owned memory. + score::cpp::span aad{}; + + // -- Fluent builder -- + + WrapKeyParams& SetKeyToWrap(const CryptoResourceId& key) noexcept + { + key_to_wrap = key; + return *this; + } + + WrapKeyParams& SetWrappingKey(const CryptoResourceId& key) noexcept + { + wrapping_key = key; + return *this; + } + + WrapKeyParams& SetWrappingAlgorithm(const AlgorithmId& alg) noexcept + { + wrapping_algorithm = alg; + return *this; + } + + WrapKeyParams& SetIv(const uint8_t* data, std::size_t len) noexcept + { + iv = {data, len}; + return *this; + } + + WrapKeyParams& SetIv(score::cpp::span data) noexcept + { + iv = data; + return *this; + } + + WrapKeyParams& SetAad(const uint8_t* data, std::size_t len) noexcept + { + aad = {data, len}; + return *this; + } + + WrapKeyParams& SetAad(score::cpp::span data) noexcept + { + aad = data; + return *this; + } +}; + +/// @brief Parameters for key unwrapping via IKeyManagementContext::UnwrapKey(). +struct UnwrapKeyParams +{ + /// @brief Wrapped key blob (required). Points to caller-owned memory. + score::cpp::span wrapped_data{}; + + /// @brief Handle to the wrapping key / KEK (required). + CryptoResourceId wrapping_key{}; + + /// @brief Algorithm of the inner (unwrapped) key (required). + AlgorithmId inner_key_algorithm{}; + + /// @brief Wrapping algorithm (optional — same as WrapKeyParams). + std::optional wrapping_algorithm{std::nullopt}; + + /// @brief IV used during wrapping (required for AES-GCM/CBC wrapping). + score::cpp::span iv{}; + + /// @brief AAD used during wrapping (for AEAD wrapping algorithms). + score::cpp::span aad{}; + + /// @brief Operations the unwrapped key is permitted to perform. + KeyOperationPermission permissions{KeyOperationPermission::kAll}; + + // -- Fluent builder -- + + UnwrapKeyParams& SetWrappedData(const uint8_t* data, std::size_t len) noexcept + { + wrapped_data = {data, len}; + return *this; + } + + UnwrapKeyParams& SetWrappedData(score::cpp::span data) noexcept + { + wrapped_data = data; + return *this; + } + + UnwrapKeyParams& SetWrappingKey(const CryptoResourceId& key) noexcept + { + wrapping_key = key; + return *this; + } + + UnwrapKeyParams& SetInnerKeyAlgorithm(const AlgorithmId& alg) noexcept + { + inner_key_algorithm = alg; + return *this; + } + + UnwrapKeyParams& SetWrappingAlgorithm(const AlgorithmId& alg) noexcept + { + wrapping_algorithm = alg; + return *this; + } + + UnwrapKeyParams& SetIv(const uint8_t* data, std::size_t len) noexcept + { + iv = {data, len}; + return *this; + } + + UnwrapKeyParams& SetIv(score::cpp::span data) noexcept + { + iv = data; + return *this; + } + + UnwrapKeyParams& SetAad(const uint8_t* data, std::size_t len) noexcept + { + aad = {data, len}; + return *this; + } + + UnwrapKeyParams& SetAad(score::cpp::span data) noexcept + { + aad = data; + return *this; + } + + UnwrapKeyParams& SetPermissions(KeyOperationPermission perms) noexcept + { + permissions = perms; + return *this; + } +}; + +/// @brief Parameters for key import via IKeyManagementContext::ImportKey(). +/// +/// Intended primarily for importing **public keys** (e.g., a peer's SubjectPublicKeyInfo +/// for signature verification or asymmetric encryption). Public keys carry no +/// confidentiality requirement and DER/PEM are their natural wire formats. +/// +/// @warning Importing plaintext private or symmetric key material via this struct +/// is strongly discouraged — it exposes secret material outside the secure boundary. +/// Use UnwrapKey() instead, which accepts the key already encrypted under a KEK +/// and never exposes the plaintext secret. +struct ImportKeyParams +{ + /// @brief Key data to import (required). Points to caller-owned memory. + /// For public keys: DER-encoded SubjectPublicKeyInfo or PEM equivalent. + score::cpp::span key_data{}; + + /// @brief Encoding format of key_data: DER (binary ASN.1) or PEM (base64-armored). + /// Both formats are meaningful only for asymmetric (public) keys. + /// Symmetric keys have no ASN.1 structure and must not be imported via this struct. + FormatType format{FormatType::kDer}; + + /// @brief Algorithm identifier for the imported key (required). + AlgorithmId algorithm{}; + + /// @brief Operations the imported key is permitted to perform. + KeyOperationPermission permissions{KeyOperationPermission::kAll}; + + // -- Fluent builder -- + + ImportKeyParams& SetKeyData(const uint8_t* data, std::size_t len) noexcept + { + key_data = {data, len}; + return *this; + } + + ImportKeyParams& SetKeyData(score::cpp::span data) noexcept + { + key_data = data; + return *this; + } + + ImportKeyParams& SetFormat(FormatType fmt) noexcept + { + format = fmt; + return *this; + } + + ImportKeyParams& SetAlgorithm(const AlgorithmId& alg) noexcept + { + algorithm = alg; + return *this; + } + + ImportKeyParams& SetPermissions(KeyOperationPermission perms) noexcept + { + permissions = perms; + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_KEY_OPERATION_PARAMS_HPP diff --git a/score/mw/crypto/api/config/mac_context_config.hpp b/score/mw/crypto/api/config/mac_context_config.hpp new file mode 100644 index 0000000..04c9f82 --- /dev/null +++ b/score/mw/crypto/api/config/mac_context_config.hpp @@ -0,0 +1,85 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_MAC_CONTEXT_CONFIG_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_MAC_CONTEXT_CONFIG_HPP + +#include "score/mw/crypto/api/config/base_context_config.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Configuration for MAC context creation. +/// +/// Requires an algorithm and a key. Typical algorithms include +/// HMAC-SHA-256, HMAC-SHA-384, CMAC-AES-128, CMAC-AES-256. +/// +/// @par Example +/// @code +/// MacContextConfig config; +/// config.SetAlgorithm("HMAC-SHA-256").SetKey(mac_key); +/// auto ctx = crypto_context->CreateMacContext(config); +/// @endcode +struct MacContextConfig : public BaseContextConfig +{ + /// @brief Handle to the MAC key (required). + /// Must be a CryptoResourceId with type == kKey. + CryptoResourceId key{}; + + // -- Fluent builder -- + + MacContextConfig& SetAlgorithm(const AlgorithmId& alg) noexcept + { + BaseContextConfig::SetAlgorithm(alg); + return *this; + } + + MacContextConfig& SetKey(const CryptoResourceId& k) noexcept + { + key = k; + return *this; + } + + MacContextConfig& SetProvider(const CryptoResourceId& prov) noexcept + { + BaseContextConfig::SetProvider(prov); + return *this; + } + + MacContextConfig& SetProviderType(ProviderType type) noexcept + { + BaseContextConfig::SetProviderType(type); + return *this; + } + + MacContextConfig& SetOperationMode(OperationMode mode) noexcept + { + BaseContextConfig::SetOperationMode(mode); + return *this; + } + + MacContextConfig& SetExtendedParameter(const std::string& key, const std::string& value) + { + BaseContextConfig::SetExtendedParameter(key, value); + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_MAC_CONTEXT_CONFIG_HPP diff --git a/score/mw/crypto/api/config/permission_builder.hpp b/score/mw/crypto/api/config/permission_builder.hpp new file mode 100644 index 0000000..53c68d9 --- /dev/null +++ b/score/mw/crypto/api/config/permission_builder.hpp @@ -0,0 +1,153 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_PERMISSION_BUILDER_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_PERMISSION_BUILDER_HPP + +#include "score/mw/crypto/api/common/types.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// Fluent builder for constructing @c KeyOperationPermission bitmasks. +/// +/// Usage: +/// @code +/// auto perms = PermissionBuilder() +/// .AllowEncrypt() +/// .AllowDecrypt() +/// .AllowExport() +/// .Build(); +/// @endcode +/// +/// The builder starts with kNone and accumulates bits via each Allow*() call. +class PermissionBuilder final +{ + public: + constexpr PermissionBuilder() noexcept = default; + + /// Start from an existing permission set. + constexpr explicit PermissionBuilder(KeyOperationPermission initial) noexcept + : m_perms{static_cast(initial)} + { + } + + // -- Individual permission setters -- + + constexpr PermissionBuilder& AllowEncrypt() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kEncrypt); + return *this; + } + constexpr PermissionBuilder& AllowDecrypt() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kDecrypt); + return *this; + } + constexpr PermissionBuilder& AllowWrap() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kWrap); + return *this; + } + constexpr PermissionBuilder& AllowUnwrap() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kUnwrap); + return *this; + } + constexpr PermissionBuilder& AllowSign() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kSign); + return *this; + } + constexpr PermissionBuilder& AllowVerify() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kVerify); + return *this; + } + constexpr PermissionBuilder& AllowMac() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kMac); + return *this; + } + constexpr PermissionBuilder& AllowAgree() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kAgree); + return *this; + } + constexpr PermissionBuilder& AllowDerive() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kDerive); + return *this; + } + constexpr PermissionBuilder& AllowExport() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kExport); + return *this; + } + constexpr PermissionBuilder& AllowImport() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kImport); + return *this; + } + + // -- Composite setters -- + + constexpr PermissionBuilder& AllowDataProtection() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kDataProtection); + return *this; + } + constexpr PermissionBuilder& AllowAuthentication() noexcept + { + m_perms |= static_cast(KeyOperationPermission::kAuthentication); + return *this; + } + constexpr PermissionBuilder& AllowAll() noexcept + { + m_perms = static_cast(KeyOperationPermission::kAll); + return *this; + } + + // -- Removal -- + + constexpr PermissionBuilder& Deny(KeyOperationPermission perm) noexcept + { + m_perms &= ~static_cast(perm); + return *this; + } + + // -- Terminal -- + + [[nodiscard]] constexpr KeyOperationPermission Build() const noexcept + { + return static_cast(m_perms); + } + + /// Implicit conversion for use as a direct argument. + [[nodiscard]] constexpr operator KeyOperationPermission() const noexcept // NOLINT + { + return static_cast(m_perms); + } + + private: + uint32_t m_perms{0U}; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_PERMISSION_BUILDER_HPP diff --git a/score/mw/crypto/api/contexts/BUILD b/score/mw/crypto/api/contexts/BUILD new file mode 100644 index 0000000..9b61481 --- /dev/null +++ b/score/mw/crypto/api/contexts/BUILD @@ -0,0 +1,71 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +#Copyright(c) 2026 by ETAS GmbH.All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "context_bases", + hdrs = [ + "i_context.hpp", + "i_streaming_context.hpp", + "i_streaming_output_context.hpp", + ], + includes = ["."], + visibility = ["//visibility:public"], + deps = [ + "//score/mw/crypto/api/common:crypto_common", + "@score_baselibs//score/language/futurecpp", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "crypto_contexts", + hdrs = [ + "i_hash_context.hpp", + "i_key_management_context.hpp", + "i_mac_context.hpp", + ], + includes = ["."], + visibility = ["//visibility:public"], + deps = [ + ":context_bases", + "//score/mw/crypto/api/common:crypto_common", + "//score/mw/crypto/api/config:context_configs", + "//score/mw/crypto/api/objects:crypto_objects", + "@score_baselibs//score/language/futurecpp", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "crypto_contexts_impl", + srcs = [ + "src/hash_context_impl.cpp", + "src/key_management_context_impl.cpp", + "src/mac_context_impl.cpp", + ], + hdrs = [ + "src/hash_context_impl.hpp", + "src/key_management_context_impl.hpp", + "src/mac_context_impl.hpp", + ], + includes = ["."], + visibility = ["//:__subpackages__"], + deps = [ + ":crypto_contexts", + "//score/crypto/api:operations", + "//score/crypto/api/control_plane", + "//score/mw/crypto/api/common:crypto_common", + "//score/mw/crypto/api/common:release_callback_iface", + ], +) diff --git a/score/mw/crypto/api/contexts/i_context.hpp b/score/mw/crypto/api/contexts/i_context.hpp new file mode 100644 index 0000000..5f0bb54 --- /dev/null +++ b/score/mw/crypto/api/contexts/i_context.hpp @@ -0,0 +1,53 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_CONTEXT_HPP + +#include "score/mw/crypto/api/common/error_domain.hpp" + +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Root base interface for all crypto operation contexts. +/// +/// Provides the common Uptr typedef and virtual destructor. All concrete +/// operation contexts (hash, encrypt, sign, etc.) ultimately derive from +/// this interface, either directly or via IStreamingContext / +/// IStreamingOutputContext. +class IContext +{ + public: + using Uptr = std::unique_ptr; + + virtual ~IContext() = default; + + IContext(const IContext&) = delete; + IContext& operator=(const IContext&) = delete; + IContext(IContext&&) = default; + IContext& operator=(IContext&&) = default; + + protected: + IContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_CONTEXT_HPP diff --git a/score/mw/crypto/api/contexts/i_hash_context.hpp b/score/mw/crypto/api/contexts/i_hash_context.hpp new file mode 100644 index 0000000..d3ad739 --- /dev/null +++ b/score/mw/crypto/api/contexts/i_hash_context.hpp @@ -0,0 +1,78 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_HASH_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_HASH_CONTEXT_HPP + +#include "score/mw/crypto/api/contexts/i_streaming_output_context.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Interface for hash/digest operations. +/// +/// Exposes Init, Update, Reset, and Finalize from base classes plus +/// hash-specific SingleShot() and GetDigestSize(). +/// Init() is exposed with the base default (iv = std::nullopt); +/// hash implementations reject a non-null IV. +/// +/// Compatible with classical algorithms (SHA-256, SHA-3) and PQC hash-based +/// schemes (e.g., XMSS/LMS hash functions, SHAKE for ML-DSA). +class IHashContext : public IStreamingOutputContext +{ + public: + using Uptr = std::unique_ptr; + + virtual ~IHashContext() = default; + + IHashContext(const IHashContext&) = delete; + IHashContext& operator=(const IHashContext&) = delete; + IHashContext(IHashContext&&) = default; + IHashContext& operator=(IHashContext&&) = default; + + // -- Streaming API (from base classes) -- + using IStreamingContext::Init; + using IStreamingContext::Reset; + using IStreamingContext::Update; + using IStreamingOutputContext::Finalize; + + /// @brief Computes the hash digest in a single call. + /// @param input Data to hash + /// @param output Buffer to receive the digest + /// @return Number of bytes written on success, error on failure + /// @note Equivalent to Init() + Update(input) + Finalize(output). + virtual score::Result SingleShot(score::cpp::span input, + score::cpp::span output) = 0; + + /// @brief Returns the digest size in bytes for the configured algorithm. + /// @note For variable-output hash functions (e.g., SHAKE), returns the + /// default output length configured at context creation. + virtual std::size_t GetDigestSize() const noexcept = 0; + + protected: + IHashContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_HASH_CONTEXT_HPP diff --git a/score/mw/crypto/api/contexts/i_key_management_context.hpp b/score/mw/crypto/api/contexts/i_key_management_context.hpp new file mode 100644 index 0000000..942aee3 --- /dev/null +++ b/score/mw/crypto/api/contexts/i_key_management_context.hpp @@ -0,0 +1,299 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_KEY_MANAGEMENT_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_KEY_MANAGEMENT_CONTEXT_HPP + +#include "score/mw/crypto/api/common/crypto_resource_guard.hpp" +#include "score/mw/crypto/api/common/types.hpp" +#include "score/mw/crypto/api/config/key_operation_params.hpp" +#include "score/mw/crypto/api/contexts/i_context.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Interface for key lifecycle management operations. +/// +/// All key-producing methods return a CryptoResourceGuard that owns the +/// ephemeral key resource. The guard releases it to the daemon when it +/// goes out of scope, ensuring keys are never silently leaked. +/// +/// The guard provides implicit conversion to `const CryptoResourceId&`, +/// so it can be passed directly to SetKey(), ExportKey(), PersistKey(), +/// and any other API that accepts CryptoResourceId — no explicit .Id() call. +/// +/// All key-producing operations (GenerateKey, DeriveKey, AgreeKey, UnwrapKey, +/// ImportKey) provide two overloads: +/// +/// - **Ephemeral overload** — takes only a params struct, returns a +/// CryptoResourceGuard wrapping the ephemeral key. The guard provides +/// RAII cleanup: daemon frees key material when the guard is destroyed. +/// +/// - **Direct-to-slot overload** — takes a target_slot CryptoResourceId as +/// the first parameter followed by the params struct, returns Result. +/// The key is generated/derived/imported directly into the persistent slot. +/// No guard is involved — the key lives in the slot and is accessed via +/// the slot-direct path. +/// +/// Each operation's parameters are encapsulated in a dedicated params struct +/// with a fluent builder API (e.g., GenerateKeyParams, DeriveKeyParams). +/// Key derivation uses structured KdfParameters with typed fields, +/// supporting HKDF, TLS 1.2 PRF, TLS 1.3 HKDF, PBKDF2, and SP800-108. +/// +/// Two key usage paths after creation: +/// - **Slot-direct**: Pass a resolved kKeySlot directly to config.SetKey(). +/// The context factory internally loads key material and releases it on +/// context destruction. No LoadKey() or guard needed. +/// - **Guard path**: Call LoadKey()/GenerateKey()/etc. to get a guard. +/// The guard auto-releases on destruction. Use this when sharing the +/// same loaded key across multiple contexts (performance optimization). +/// +/// Supports classical key types (AES, RSA, ECC) and PQC key types: +/// - ML-KEM (Kyber) for key encapsulation +/// - ML-DSA (Dilithium) for signatures +/// - SLH-DSA (SPHINCS+) for stateless hash-based signatures +/// - XMSS/LMS for stateful hash-based signatures +/// +/// PQC key generation may produce larger keys — call GetExportKeySize() +/// before ExportKey() to determine the required buffer size. +class IKeyManagementContext : public IContext +{ + public: + using Uptr = std::unique_ptr; + + ~IKeyManagementContext() override = default; + + IKeyManagementContext(const IKeyManagementContext&) = delete; + IKeyManagementContext& operator=(const IKeyManagementContext&) = delete; + IKeyManagementContext(IKeyManagementContext&&) = default; + IKeyManagementContext& operator=(IKeyManagementContext&&) = default; + + // ========================================================================= + // Key Generation + // ========================================================================= + + /// @brief Generates a new ephemeral key (symmetric or asymmetric). + /// + /// For symmetric algorithms (AES), returns a single symmetric key. + /// For asymmetric algorithms (RSA, ECDH, ML-DSA, etc.), returns the private key. + /// The public key can be derived on-demand via IPrivateKeyObject::GetPublicKey(). + /// + /// @param params Key generation parameters (algorithm, permissions). + /// @return CryptoResourceGuard wrapping the ephemeral key (symmetric or private). + virtual score::Result GenerateKey(const GenerateKeyParams& params) = 0; + + /// @brief Generates a key directly into a persistent key slot. + /// + /// For symmetric algorithms, occupies only the target_slot. + /// For asymmetric algorithms, occupies target_slot (private) and optionally + /// public_slot (public key). If public_slot is omitted, the public key + /// remains ephemeral and must be derived when needed. + /// + /// @param target_slot Handle to the target key slot (type = kKeySlot). + /// For asymmetric: holds private key. + /// For symmetric: holds the symmetric key. + /// @param public_slot Optional second slot for public key (asymmetric only). + /// Ignored for symmetric algorithms. If provided for asymmetric, + /// the public key is generated directly into this slot. + /// @param params Key generation parameters (algorithm, permissions). + /// @return std::monostate on success; error if slot is occupied, policy violation, etc. + virtual score::Result GenerateKey(const CryptoResourceId& target_slot, + const std::optional& public_slot, + const GenerateKeyParams& params) = 0; + + // ========================================================================= + // Key Derivation + // ========================================================================= + + /// @brief Derives a new ephemeral key using structured KDF parameters. + /// @param params Derivation parameters (source key, algorithm, KDF config, permissions). + /// Implementations shall call params.Validate() before IPC dispatch to catch + /// KDF truncation errors. + /// @return CryptoResourceGuard wrapping the ephemeral derived key. + /// @see KdfParameters for supported KDFs (HKDF, TLS 1.2 PRF, TLS 1.3, PBKDF2, SP800-108). + virtual score::Result DeriveKey(const DeriveKeyParams& params) = 0; + + /// @brief Derives a key directly into a persistent key slot. + /// @param target_slot Handle to the target key slot (type = kKeySlot). + /// @param params Derivation parameters. Implementations shall call params.Validate(). + /// @return std::monostate on success. + virtual score::Result DeriveKey(const CryptoResourceId& target_slot, + const DeriveKeyParams& params) = 0; + + // ========================================================================= + // Key Agreement + // ========================================================================= + + /// @brief Performs key agreement (ECDH, X25519, ML-KEM decapsulation). + /// + /// When AgreeKeyParams::kdf is set, the daemon shall perform agreement + KDF + /// atomically in a single IPC call, returning the derived key directly. + /// When kdf is omitted, the raw shared secret is returned. + /// + /// @param params Agreement parameters (private key, peer public key, + /// agreement algorithm, optional KDF for combined agree+derive). + /// Implementations shall call params.Validate() before IPC dispatch to catch + /// KDF truncation errors. + /// @return CryptoResourceGuard wrapping the agreed/derived key. + virtual score::Result AgreeKey(const AgreeKeyParams& params) = 0; + + /// @brief Performs key agreement directly into a persistent key slot. + /// @param target_slot Handle to the target key slot (type = kKeySlot). + /// @param params Agreement parameters. Implementations shall call params.Validate() before IPC dispatch. + /// @return std::monostate on success. + virtual score::Result AgreeKey(const CryptoResourceId& target_slot, + const AgreeKeyParams& params) = 0; + + // ========================================================================= + // Persistence + // ========================================================================= + + /// @brief Copies an ephemeral key to a persistent key slot. + /// + /// The ephemeral key (and its guard) remains valid after this call and is + /// released independently when the guard goes out of scope. Use the guard's + /// implicit conversion to pass it directly: + /// + /// @param target_slot Handle to the target persistent key slot (type = kKeySlot). + /// @param ephemeral_key CryptoResourceId of the ephemeral key (type = kKey). + /// @return std::monostate on success, error if not allowed by policy or slot is occupied. + virtual score::Result PersistKey(const CryptoResourceId& target_slot, + const CryptoResourceId& ephemeral_key) = 0; + + // ========================================================================= + // Key Unwrapping + // ========================================================================= + + /// @brief Unwraps (decrypts) a wrapped key blob into an ephemeral key. + /// @param params Unwrap parameters (wrapped data, wrapping key, inner key algorithm, + /// optional wrapping algorithm, IV, AAD, permissions). + /// @return CryptoResourceGuard wrapping the ephemeral unwrapped key. + virtual score::Result UnwrapKey(const UnwrapKeyParams& params) = 0; + + /// @brief Unwraps a key directly into a persistent key slot. + /// @param target_slot Handle to the target key slot (type = kKeySlot). + /// @param params Unwrap parameters. + /// @return std::monostate on success. + virtual score::Result UnwrapKey(const CryptoResourceId& target_slot, + const UnwrapKeyParams& params) = 0; + + // ========================================================================= + // Key Import + // ========================================================================= + + /// @brief Imports key data into an ephemeral key. + /// @param params Import parameters (key data, format, algorithm, permissions). + /// @return CryptoResourceGuard wrapping the ephemeral imported key. + virtual score::Result ImportKey(const ImportKeyParams& params) = 0; + + /// @brief Imports key data directly into a persistent key slot. + /// @param target_slot Handle to the target key slot (type = kKeySlot). + /// @param params Import parameters. + /// @return std::monostate on success. + virtual score::Result ImportKey(const CryptoResourceId& target_slot, + const ImportKeyParams& params) = 0; + + // ========================================================================= + // Key Loading (optional — advanced path for multi-context reuse) + // ========================================================================= + + /// @brief Loads a persistent key from a key slot for use in operations. + /// @param slot Handle to the persistent key slot (type = kKeySlot). + /// @return CryptoResourceGuard wrapping the loaded key. + /// + /// @note LoadKey is optional. The simpler slot-direct path passes a kKeySlot + /// directly to config.SetKey(); the context factory loads internally. + virtual score::Result LoadKey(const CryptoResourceId& slot) = 0; + + // ========================================================================= + // Key Wrapping + // ========================================================================= + + /// @brief Wraps (encrypts) a key using a wrapping key. + /// + /// @param params Wrap parameters (key to wrap, wrapping key, optional algorithm/IV/AAD). + /// @param output Buffer to receive the wrapped key data. + /// @return Number of bytes written on success. + virtual score::Result WrapKey(const WrapKeyParams& params, score::cpp::span output) = 0; + + /// @brief Returns the byte length of the wrapped form of a key. + /// + /// Call this before WrapKey() to allocate a buffer of the correct size. + /// The size depends on the wrapping algorithm (e.g., AES key wrap adds + /// 8 bytes of overhead per RFC 3394). + /// + /// @param params Wrap parameters (key to wrap, wrapping key, optional algorithm/IV/AAD). + /// @return Required buffer size in bytes, or error if wrapping is not permitted + virtual score::Result GetWrapKeySize(const WrapKeyParams& params) = 0; + + // ========================================================================= + // Key Export + // ========================================================================= + + /// @brief Exports key data in the specified format. + /// @param key Handle to the key to export. + /// @param output Buffer to receive the exported key data. + /// @param format Output encoding format (default: DER). + /// @return Number of bytes written on success, error if key is not exportable. + virtual score::Result ExportKey(const CryptoResourceId& key, + score::cpp::span output, + FormatType format = FormatType::kDer) = 0; + + /// @brief Returns the byte length of the exported form of a key. + /// + /// Call this before ExportKey() to allocate a buffer of the correct size. + /// The size depends on the algorithm and export format (e.g., DER-encoded + /// PKCS#8 for private keys, SubjectPublicKeyInfo for public keys). + /// + /// @param key Handle to the key to query + /// @param format Desired export format (default: DER) + /// @return Required buffer size in bytes, or error if the key is not exportable + virtual score::Result GetExportKeySize(const CryptoResourceId& key, + FormatType format = FormatType::kDer) = 0; + + // ========================================================================= + // Key Clearing + // ========================================================================= + + /// @brief Clears a key or key slot. + /// @param key Handle to the key or key slot to clear. + /// @return std::monostate on success. + virtual score::Result ClearKey(const CryptoResourceId& key) = 0; + + // ========================================================================= + // Slot Queries + // ========================================================================= + + /// @brief Queries the state and metadata of a key slot. + /// @param slot Handle to the key slot (type = kKeySlot). + /// @return KeySlotInfo containing state, algorithm, and provider binding. + virtual score::Result GetKeySlotInfo(const CryptoResourceId& slot) = 0; + + protected: + IKeyManagementContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_KEY_MANAGEMENT_CONTEXT_HPP diff --git a/score/mw/crypto/api/contexts/i_mac_context.hpp b/score/mw/crypto/api/contexts/i_mac_context.hpp new file mode 100644 index 0000000..360a070 --- /dev/null +++ b/score/mw/crypto/api/contexts/i_mac_context.hpp @@ -0,0 +1,75 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_MAC_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_MAC_CONTEXT_HPP + +#include "score/mw/crypto/api/contexts/i_streaming_output_context.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Interface for message authentication code (MAC) operations. +/// +/// Exposes Init, Update, Reset, and Finalize from base classes plus +/// MAC-specific Verify() and GetMacSize(). +/// Init() uses the optional-IV base signature; passing a value enables +/// algorithms like GMAC, while std::nullopt (the default) is used for +/// HMAC/CMAC. +/// +/// Compatible with HMAC-SHA256, CMAC-AES, GMAC, and other MAC algorithms. +class IMacContext : public IStreamingOutputContext +{ + public: + using Uptr = std::unique_ptr; + + ~IMacContext() override = default; + + IMacContext(const IMacContext&) = delete; + IMacContext& operator=(const IMacContext&) = delete; + IMacContext(IMacContext&&) = default; + IMacContext& operator=(IMacContext&&) = default; + + // -- Streaming API (from base classes) -- + using IStreamingContext::Init; + using IStreamingContext::Reset; + using IStreamingContext::Update; + using IStreamingOutputContext::Finalize; + + /// @brief Verifies a MAC against the accumulated data. + /// @param mac The MAC value to verify + /// @return true if MAC is valid, false if invalid, error on failure + /// @note Must be called after Update() calls. Alternative to Finalize() + /// when you want to verify rather than produce a MAC. + virtual score::Result Verify(score::cpp::span mac) = 0; + + /// @brief Returns the MAC size in bytes for the configured algorithm. + virtual std::size_t GetMacSize() const noexcept = 0; + + protected: + IMacContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_MAC_CONTEXT_HPP diff --git a/score/mw/crypto/api/contexts/i_streaming_context.hpp b/score/mw/crypto/api/contexts/i_streaming_context.hpp new file mode 100644 index 0000000..b428f79 --- /dev/null +++ b/score/mw/crypto/api/contexts/i_streaming_context.hpp @@ -0,0 +1,106 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_STREAMING_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_STREAMING_CONTEXT_HPP + +#include "score/mw/crypto/api/contexts/i_context.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Base interface for streaming crypto operations (Init → Update* pattern). +/// +/// Provides the common Init(), Update(), and Reset() methods as protected +/// virtual functions. Derived context interfaces (IHashContext, IMacContext, +/// ICipherContext, etc.) selectively expose these in their public sections +/// via using-declarations, so that each context presents a self-contained +/// API surface to the user. +/// +/// Init() accepts an optional initialization vector to support algorithms +/// that require one (e.g. GMAC, AES-CBC). Contexts that do not use an IV +/// simply call Init() with the default std::nullopt. +class IStreamingContext : public IContext +{ + public: + using Uptr = std::unique_ptr; + + virtual ~IStreamingContext() = default; + + IStreamingContext(const IStreamingContext&) = delete; + IStreamingContext& operator=(const IStreamingContext&) = delete; + IStreamingContext(IStreamingContext&&) = default; + IStreamingContext& operator=(IStreamingContext&&) = default; + + protected: + IStreamingContext() = default; + + /// @brief Initializes the streaming operation. + /// @param iv Optional initialization vector. Derived contexts expose this + /// selectively: hash/sign contexts default to std::nullopt, + /// MAC contexts accept an optional IV (e.g. GMAC), and + /// cipher/AEAD contexts require a mandatory IV via their own + /// public Init(span) signature. + /// @return std::monostate on success, error if context is in an invalid state + /// @note Must be called before Update(). Calling Init() on an already-active + /// stream resets the context for a new operation. + virtual score::Result Init(std::optional> iv = std::nullopt) = 0; + + /// @brief Feeds data into the streaming operation. + /// @param data Input data chunk + /// @return std::monostate on success, error if Init() was not called + /// @note Can be called multiple times after Init(). + virtual score::Result Update(score::cpp::span data) = 0; + + /// @brief Resets the context to its post-construction state for reuse. + /// + /// After a streaming sequence completes (Finalize / SignFinalize / + /// VerifyFinalize / VerifyAndFinalize) or when a sequence is aborted + /// mid-stream, Reset() returns the context to its initial state — + /// equivalent to when it was first obtained from the factory. + /// + /// The key binding, algorithm, and configuration established at context + /// creation are **preserved** — only the streaming state machine and + /// any accumulated intermediate data are cleared. This avoids the + /// factory + IPC round-trip cost of creating a new context. + /// + /// Typical reuse cycle: + /// Init() → Update()* → Finalize() → Reset() → Init() → ... + /// + /// @return std::monostate on success; error if the context is in a state that + /// cannot be reset (e.g., already destroyed) or if the daemon + /// fails to clear internal state. + /// + /// @post The context is in the same state as immediately after factory + /// creation. Init() must be called before the next Update(). + /// + /// @note Calling Reset() on an already-idle (post-construction or + /// post-Reset) context is a no-op that returns success. + /// @note An IPC call to the daemon is required so that provider-side + /// resources (e.g., OpenSSL EVP_MD_CTX) are also cleared. + virtual score::Result Reset() = 0; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_STREAMING_CONTEXT_HPP diff --git a/score/mw/crypto/api/contexts/i_streaming_output_context.hpp b/score/mw/crypto/api/contexts/i_streaming_output_context.hpp new file mode 100644 index 0000000..cb81300 --- /dev/null +++ b/score/mw/crypto/api/contexts/i_streaming_output_context.hpp @@ -0,0 +1,70 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_STREAMING_OUTPUT_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_STREAMING_OUTPUT_CONTEXT_HPP + +#include "score/mw/crypto/api/contexts/i_streaming_context.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Base interface for streaming operations that produce output (Init → Update* → Finalize). +/// +/// Extends IStreamingContext with protected Finalize() and GetOutputSize() +/// methods. Derived context interfaces selectively expose these in their +/// public sections via using-declarations. +/// +/// Used as base for: IHashContext, IMacContext, ICipherContext, ISignContext. +class IStreamingOutputContext : public IStreamingContext +{ + public: + using Uptr = std::unique_ptr; + + virtual ~IStreamingOutputContext() = default; + + IStreamingOutputContext(const IStreamingOutputContext&) = delete; + IStreamingOutputContext& operator=(const IStreamingOutputContext&) = delete; + IStreamingOutputContext(IStreamingOutputContext&&) = default; + IStreamingOutputContext& operator=(IStreamingOutputContext&&) = default; + + protected: + IStreamingOutputContext() = default; + + /// @brief Finalizes the streaming operation and writes the result. + /// @param output Buffer to receive the output data + /// @return Number of bytes written on success, error on failure + /// @note After calling Finalize(), Init() must be called again before + /// starting a new operation. + virtual score::Result Finalize(score::cpp::span output) = 0; + + /// @brief Returns the expected output size in bytes. + /// @note For hash, this is the digest size. For encrypt/decrypt, this depends + /// on the algorithm and the amount of data processed. Call after Init() + /// for meaningful results. + virtual std::size_t GetOutputSize() const noexcept = 0; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_STREAMING_OUTPUT_CONTEXT_HPP diff --git a/score/mw/crypto/api/contexts/src/hash_context_impl.cpp b/score/mw/crypto/api/contexts/src/hash_context_impl.cpp new file mode 100644 index 0000000..ec9334e --- /dev/null +++ b/score/mw/crypto/api/contexts/src/hash_context_impl.cpp @@ -0,0 +1,350 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/mw/crypto/api/contexts/src/hash_context_impl.hpp" + +#include "score/mw/crypto/api/common/error_domain.hpp" +#include "score/mw/crypto/api/common/types.hpp" + +#include "score/crypto/api/control_plane/i_connection.hpp" +#include "score/crypto/daemon/common/actors.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/daemon/mediator/mediator_operations.hpp" +#include "score/crypto/daemon/provider/handler/operations/hash_handler_operations.hpp" + +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +namespace proto = ::score::crypto::daemon::control_plane::protocol; +namespace actors = ::score::crypto::daemon::common::actors; +namespace hash_ops = ::score::crypto::daemon::provider::handler::hash_handler_operations; + +HashContextImpl::HashContextImpl(std::shared_ptr connection, + uint64_t context_id, + AlgorithmId algorithm) + : m_connection(std::move(connection)), m_context_id(context_id), m_algorithm(algorithm) +{ +} + +HashContextImpl::~HashContextImpl() +{ + if (!m_connection) + { + std::cerr << "[API][HashContextImpl] ERROR: Connection is not initialized during destruction\n"; + return; + } + + // Build CONTEXT_CLOSE request + auto context_close_res = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation(score::crypto::daemon::mediator::operations::CloseContext()) + .build(); + + if (!context_close_res.has_value()) + { + std::cerr << "[API][HashContextImpl] ERROR: Failed to build CTX_CLOSE request during destruction\n"; + return; + } + + // Send CTX_CLOSE request to daemon + auto response_res = m_connection->SendRequest(context_close_res.value()); + + // Validate response using ControlResponseValidator + auto validator = proto::ControlResponseValidator::FromResult(response_res); + validator.expectOperation(score::crypto::daemon::mediator::operations::CloseContext()).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][HashContextImpl] ERROR: CTX_CLOSE response validation failed: " << validator.getError() + << "\n"; + return; + } +} + +score::Result HashContextImpl::Init(std::optional> iv) +{ + if (iv.has_value()) + { + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kUnsupportedOperation, "Init with IV not supported for hash contexts")}; + } + + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_INIT}) + .build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][HashContextImpl] ERROR: Failed to build HASH_INIT request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "Failed to build HASH_INIT request")}; + } + + // Send HASH_INIT request to daemon + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + // Validate HASH_INIT response + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_INIT}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][HashContextImpl] ERROR: " << validator.getError() << "\n"; + // TODO(error-unification phase-4): Extract the specific CryptoErrorCode from the daemon + // response (via validator.getErrorCode() or ControlResponseValidator extension) and + // return it directly instead of the generic kOperationFailed. This gives callers + // actionable error information (e.g. kStreamNotInitialized vs kAlgorithmExecutionFailed) + // rather than a single catch-all code. Applies to all Init/Update/Finalize/SingleShot + // operations in every context impl (hash, mac, cipher, key_mgmt). + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "HASH_INIT daemon response invalid")}; + } + + return std::monostate{}; +} + +score::Result HashContextImpl::Update(score::cpp::span data) +{ + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_UPDATE}) + .with_in_data_buffer(data) + .build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][HashContextImpl] ERROR: Failed to build HASH_UPDATE request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "Failed to build HASH_UPDATE request")}; + } + + // Send HASH_UPDATE request to daemon + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + // Validate HASH_UPDATE response + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_UPDATE}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][HashContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "HASH_UPDATE daemon response invalid")}; + } + + return std::monostate{}; +} + +score::Result HashContextImpl::Finalize(score::cpp::span output) +{ + using proto::DataBufferReturn; + + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_FINISH}) + .build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][HashContextImpl] ERROR: Failed to build HASH_FINISH request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "Failed to build HASH_FINISH request")}; + } + + // Send HASH_FINISH request to daemon + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + // Validate HASH_FINISH response + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_FINISH}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][HashContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "HASH_FINISH daemon response invalid")}; + } + + auto hash_result = validator.getParameterAt(0, 0); + if (!hash_result.has_value()) + { + std::cerr << "[API][HashContextImpl] ERROR: HASH_FINISH response has invalid parameter type\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kOperationFailed, "HASH_FINISH response has invalid parameter type")}; + } + + const auto& hash_data = hash_result.value(); + auto bytes_to_copy = std::min(hash_data.size(), output.size()); + std::memcpy(output.data(), hash_data.data(), bytes_to_copy); + + // TODO: Consider if we should just abort here and not write anything to the output buffer + // We may also be able to get the required out size as soon as we have created the ctx + // at least a sub-set of operations / algorithms. + if (bytes_to_copy < hash_data.size()) + { + std::cerr << "[API][HashContextImpl] ERROR: Output buffer too small for full hash, truncated copy performed\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kInsufficientBufferSize, + "ERROR: Output buffer too small for full hash, truncated copy performed")}; + } + + return bytes_to_copy; +} + +score::Result HashContextImpl::SingleShot(score::cpp::span input, + score::cpp::span output) +{ + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_SS}) + .with_in_data_buffer(input) + .build(); + + if (!control_req_result.has_value()) + { + std::cerr << "[API][HashContextImpl] ERROR: Failed to build HASH_SS request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "Failed to build HASH_SS request")}; + } + + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + // Validate HASH_SS response + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_SS}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][HashContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "HASH_SS daemon response invalid")}; + } + + auto hash_result = validator.getParameterAt(0, 0); + if (!hash_result.has_value()) + { + std::cerr << "[API][HashContextImpl] ERROR: HASH_SS response has invalid parameter type\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kOperationFailed, "HASH_SS response has invalid parameter type")}; + } + + const auto& hash_data = hash_result.value(); + auto bytes_to_copy = std::min(hash_data.size(), output.size()); + std::memcpy(output.data(), hash_data.data(), bytes_to_copy); + + // TODO: Consider if we should just abort here and not write anything to the output buffer + // We may also be able to get the required out size as soon as we have created the ctx + // at least a sub-set of operations / algorithms. + if (bytes_to_copy < hash_data.size()) + { + std::cerr << "[API][HashContextImpl] ERROR: Output buffer too small for full hash, truncated copy performed\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kInsufficientBufferSize, + "ERROR: Output buffer too small for full hash, truncated copy performed")}; + } + + return bytes_to_copy; +} + +score::Result HashContextImpl::Reset() +{ + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_RESET}) + .build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][HashContextImpl] ERROR: Failed to build HASH_RESET request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "Failed to build HASH_RESET request")}; + } + + // Send HASH_RESET request to daemon + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + // Validate HASH_RESET response + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_RESET}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][HashContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "HASH_RESET daemon response invalid")}; + } + + return std::monostate{}; +} + +std::size_t HashContextImpl::GetDigestSize() const noexcept +{ + // For hash contexts, digest size and output size are the same. + return GetOutputSize(); +} + +std::size_t HashContextImpl::GetOutputSize() const noexcept +{ + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_GET_DIGEST_SIZE}) + .build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][HashContextImpl] ERROR: Failed to build HASH_GET_DIGEST_SIZE request\n"; + return 0; + } + + // Send HASH_GET_DIGEST_SIZE request to daemon + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + // Validate HASH_GET_DIGEST_SIZE response + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_GET_DIGEST_SIZE}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][HashContextImpl] ERROR: " << validator.getError() << "\n"; + return 0; + } + + auto size_result = validator.getParameterAt(0, 0); + if (!size_result.has_value()) + { + std::cerr << "[API][HashContextImpl] ERROR: HASH_GET_DIGEST_SIZE response has invalid parameter type\n"; + return 0; + } + + return static_cast(size_result.value()); +} + +} // namespace crypto +} // namespace mw +} // namespace score diff --git a/score/mw/crypto/api/contexts/src/hash_context_impl.hpp b/score/mw/crypto/api/contexts/src/hash_context_impl.hpp new file mode 100644 index 0000000..a601fc6 --- /dev/null +++ b/score/mw/crypto/api/contexts/src/hash_context_impl.hpp @@ -0,0 +1,75 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_SRC_HASH_CONTEXT_IMPL_HPP +#define SCORE_MW_CRYPTO_API_SRC_HASH_CONTEXT_IMPL_HPP + +#include "score/mw/crypto/api/contexts/i_hash_context.hpp" + +#include "score/mw/crypto/api/common/types.hpp" + +#include "score/crypto/api/control_plane/i_connection.hpp" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Concrete IHashContext implementation that delegates to the crypto daemon via IPC. +/// +/// Each instance is bound to a daemon-side hash context (identified by context_id) +/// created during construction. All streaming and single-shot operations are +/// forwarded to the daemon through the session's RequestOperation() API. +class HashContextImpl final : public IHashContext +{ + public: + /// @brief Constructs a hash context bound to an existing daemon-side context. + /// @param connection Shared connection for IPC communication (contains DataNodeId) + /// @param context_id Daemon-assigned context identifier (from CTX_CREATE response) + /// @param algorithm Algorithm name (e.g., "SHA-256") for digest size queries + HashContextImpl(std::shared_ptr connection, + uint64_t context_id, + AlgorithmId algorithm); + + ~HashContextImpl() override; + + // -- IStreamingContext -- + score::Result Init(std::optional> iv) override; + score::Result Update(score::cpp::span data) override; + score::Result Reset() override; + + // -- IStreamingOutputContext -- + score::Result Finalize(score::cpp::span output) override; + std::size_t GetOutputSize() const noexcept override; + + // -- IHashContext -- + score::Result SingleShot(score::cpp::span input, + score::cpp::span output) override; + std::size_t GetDigestSize() const noexcept override; + + private: + std::shared_ptr m_connection; + score::crypto::daemon::control_plane::protocol::DataNodeId m_context_id; + AlgorithmId m_algorithm; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_SRC_HASH_CONTEXT_IMPL_HPP diff --git a/score/mw/crypto/api/contexts/src/key_management_context_impl.cpp b/score/mw/crypto/api/contexts/src/key_management_context_impl.cpp new file mode 100644 index 0000000..ad83ad7 --- /dev/null +++ b/score/mw/crypto/api/contexts/src/key_management_context_impl.cpp @@ -0,0 +1,419 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/mw/crypto/api/contexts/src/key_management_context_impl.hpp" + +#include "score/mw/crypto/api/common/crypto_resource_guard.hpp" +#include "score/mw/crypto/api/common/error_domain.hpp" +#include "score/mw/crypto/api/common/src/crypto_resource_guard_factory.hpp" +#include "score/mw/crypto/api/common/src/i_release_callback.hpp" +#include "score/mw/crypto/api/common/types.hpp" +#include "score/mw/crypto/api/config/key_operation_params.hpp" + +#include "score/crypto/api/control_plane/i_connection.hpp" +#include "score/crypto/daemon/common/actors.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/daemon/key_management/interfaces/key_management_operations.hpp" +#include "score/crypto/daemon/mediator/mediator_operations.hpp" + +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +namespace proto = ::score::crypto::daemon::control_plane::protocol; +namespace actors = ::score::crypto::daemon::common::actors; +namespace keymgmt_ops = ::score::crypto::daemon::key_management::operations; + +// --------------------------------------------------------------------------- +// IReleaseCallback implementation — sends CTX_CLOSE on last reference drop +// --------------------------------------------------------------------------- +class KeyManagementContextImpl::ContextReleaseCallbackImpl final : public IReleaseCallback +{ + public: + ContextReleaseCallbackImpl(std::shared_ptr connection, + proto::DataNodeId context_id) + : m_connection(std::move(connection)), m_context_id(context_id) + { + } + + // No copy, no move. Just handled via shared_ptr + ContextReleaseCallbackImpl(const ContextReleaseCallbackImpl&) = delete; + ContextReleaseCallbackImpl& operator=(const ContextReleaseCallbackImpl&) = delete; + ContextReleaseCallbackImpl(ContextReleaseCallbackImpl&&) = delete; + ContextReleaseCallbackImpl& operator=(ContextReleaseCallbackImpl&&) = delete; + + ~ContextReleaseCallbackImpl() override + { + if (!m_connection) + { + std::cerr << "[API][KeyMgmtContextImpl] ERROR: Connection is not initialized during CTX_CLOSE\n"; + return; + } + + auto context_close_res = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation(score::crypto::daemon::mediator::operations::CloseContext()) + .build(); + + if (!context_close_res.has_value()) + { + std::cerr << "[API][KeyMgmtContextImpl] ERROR: Failed to build CTX_CLOSE request\n"; + return; + } + + auto response_res = m_connection->SendRequest(context_close_res.value()); + + auto validator = proto::ControlResponseValidator::FromResult(response_res); + validator.expectOperation(score::crypto::daemon::mediator::operations::CloseContext()).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][KeyMgmtContextImpl] ERROR: CTX_CLOSE response validation failed: " + << validator.getError() << "\n"; + return; + } + } + + score::Result ReleaseResource(const CryptoResourceId& /*id*/) noexcept override + { + return std::monostate{}; + } + + private: + std::shared_ptr m_connection; + proto::DataNodeId m_context_id; +}; + +// --------------------------------------------------------------------------- +// IReleaseCallback implementation — sends KEY_RELEASE to daemon +// --------------------------------------------------------------------------- +class KeyManagementContextImpl::ReleaseCallbackImpl final : public IReleaseCallback +{ + public: + ReleaseCallbackImpl(std::shared_ptr connection, + proto::DataNodeId context_id, + std::shared_ptr context_release_callback) + : m_connection(std::move(connection)), + m_context_id(context_id), + m_context_release_callback(std::move(context_release_callback)) + { + } + + score::Result ReleaseResource(const CryptoResourceId& id) noexcept override + { + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_KEY_MANAGEMENT, keymgmt_ops::KEY_RELEASE}) + .with_in_val_uint64(id.id) + .build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][KeyMgmtRelease] ERROR: Failed to build KEY_RELEASE request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "Failed to build KEY_RELEASE request")}; + } + + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_KEY_MANAGEMENT, keymgmt_ops::KEY_RELEASE}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][KeyMgmtRelease] ERROR: " << validator.getError() << "\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "KEY_RELEASE daemon response invalid")}; + } + + return std::monostate{}; + } + + private: + std::shared_ptr m_connection; + proto::DataNodeId m_context_id; + std::shared_ptr m_context_release_callback; +}; + +KeyManagementContextImpl::KeyManagementContextImpl( + std::shared_ptr connection, + uint64_t context_id) + : m_connection(std::move(connection)), + m_context_id(context_id), + m_context_release_callback(std::make_shared(m_connection, m_context_id)), + m_release_callback(std::make_shared(m_connection, m_context_id, m_context_release_callback)) +{ +} + +KeyManagementContextImpl::~KeyManagementContextImpl() = default; + +// --------------------------------------------------------------------------- +// Key Generation — Ephemeral +// --------------------------------------------------------------------------- + +score::Result KeyManagementContextImpl::GenerateKey(const GenerateKeyParams& params) +{ + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_KEY_MANAGEMENT, keymgmt_ops::KEY_GENERATE}) + .with_in_string(params.algorithm) + .with_in_val_uint32(static_cast(params.permissions)) + .build(); + + if (!control_req_result.has_value()) + { + std::cerr << "[API][KeyMgmtContextImpl] ERROR: Failed to build KEY_GENERATE request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kKeyGenerationFailed, "Failed to build KEY_GENERATE request")}; + } + + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_KEY_MANAGEMENT, keymgmt_ops::KEY_GENERATE}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][KeyMgmtContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kKeyGenerationFailed, "KEY_GENERATE daemon response invalid")}; + } + + // Extract the daemon-assigned resource id from the response + auto resource_id_result = validator.getParameterAt(0, 0); + if (!resource_id_result.has_value()) + { + std::cerr << "[API][KeyMgmtContextImpl] ERROR: KEY_GENERATE response has invalid resource_id type\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kKeyGenerationFailed, "KEY_GENERATE response has invalid resource_id type")}; + } + + CryptoResourceId resource_id{}; + resource_id.id = resource_id_result.value(); + resource_id.type = ResourceType::kKey; + resource_id.persistence = ResourcePersistence::kEphemeral; + + // Extract optional provider id from response (param 0, index 1) + auto provider_result = validator.getParameterAt(0, 1); + if (provider_result.has_value()) + { + resource_id.primary_provider = provider_result.value(); + } + + return CryptoResourceGuardFactory::Make(m_release_callback, resource_id); +} + +score::Result KeyManagementContextImpl::LoadKey(const CryptoResourceId& slot) +{ + if (slot.type != ResourceType::kKeySlot) + { + std::cerr << "[API][KeyMgmtContextImpl] ERROR: LoadKey called with invalid slot resource type\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kInvalidArgument, "LoadKey called with invalid slot resource type")}; + } + + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_KEY_MANAGEMENT, keymgmt_ops::KEY_LOAD}) + .with_in_val_uint64(slot.id) + .build(); + + if (!control_req_result.has_value()) + { + std::cerr << "[API][KeyMgmtContextImpl] ERROR: Failed to build KEY_LOAD request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kInternalError, "Failed to build KEY_LOAD request")}; + } + + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_KEY_MANAGEMENT, keymgmt_ops::KEY_LOAD}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][KeyMgmtContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kInternalError, "KEY_LOAD daemon response invalid")}; + } + + // Extract the daemon-assigned resource id for the loaded key material + auto resource_id_result = validator.getParameterAt(0, 0); + if (!resource_id_result.has_value()) + { + std::cerr << "[API][KeyMgmtContextImpl] ERROR: KEY_LOAD response has invalid resource_id type\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kInternalError, "KEY_LOAD response has invalid resource_id type")}; + } + + CryptoResourceId resource_id{}; + resource_id.id = resource_id_result.value(); + resource_id.type = ResourceType::kKey; + resource_id.persistence = ResourcePersistence::kEphemeral; + + // Extract optional provider id from response (param 0, index 1) + auto provider_result = validator.getParameterAt(0, 1); + if (provider_result.has_value()) + { + resource_id.primary_provider = provider_result.value(); + } + + return CryptoResourceGuardFactory::Make(m_release_callback, resource_id); +} + +// --------------------------------------------------------------------------- +// Stubs — not yet implemented +// --------------------------------------------------------------------------- +score::Result KeyManagementContextImpl::GenerateKey( + const CryptoResourceId& /*target_slot*/, + const std::optional& /*public_slot*/, + const GenerateKeyParams& /*params*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: GenerateKey with target_slot not yet implemented\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kUnsupportedOperation, "GenerateKey with target_slot not yet implemented")}; +} + +score::Result KeyManagementContextImpl::DeriveKey(const DeriveKeyParams& /*params*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: DeriveKey not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "DeriveKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::DeriveKey(const CryptoResourceId& /*target_slot*/, + const DeriveKeyParams& /*params*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: DeriveKey not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "DeriveKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::AgreeKey(const AgreeKeyParams& /*params*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: AgreeKey not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "AgreeKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::AgreeKey(const CryptoResourceId& /*target_slot*/, + const AgreeKeyParams& /*params*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: AgreeKey not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "AgreeKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::PersistKey(const CryptoResourceId& /*target_slot*/, + const CryptoResourceId& /*ephemeral_key*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: PersistKey not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "PersistKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::UnwrapKey(const UnwrapKeyParams& /*params*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: UnwrapKey not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "UnwrapKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::UnwrapKey(const CryptoResourceId& /*target_slot*/, + const UnwrapKeyParams& /*params*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: UnwrapKey not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "UnwrapKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::ImportKey(const ImportKeyParams& /*params*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: ImportKey not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "ImportKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::ImportKey(const CryptoResourceId& /*target_slot*/, + const ImportKeyParams& /*params*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: ImportKey not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "ImportKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::WrapKey(const WrapKeyParams& /*params*/, + score::cpp::span /*output*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: WrapKey not yet implemented\n"; + return score::Result{score::unexpect, + MakeError(CryptoErrorCode::kUnsupportedOperation, "WrapKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::GetWrapKeySize(const WrapKeyParams& /*params*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: GetWrapKeySize not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "GetWrapKeySize not yet implemented")}; +} + +score::Result KeyManagementContextImpl::ExportKey(const CryptoResourceId& /*key*/, + score::cpp::span /*output*/, + FormatType /*format*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: ExportKey not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "ExportKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::GetExportKeySize(const CryptoResourceId& /*key*/, + FormatType /*format*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: GetExportKeySize not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "GetExportKeySize not yet implemented")}; +} + +score::Result KeyManagementContextImpl::ClearKey(const CryptoResourceId& /*key*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: ClearKey not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "ClearKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::GetKeySlotInfo(const CryptoResourceId& /*slot*/) +{ + std::cerr << "[API][KeyMgmtContextImpl] ERROR: GetKeySlotInfo not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "GetKeySlotInfo not yet implemented")}; +} + +} // namespace crypto +} // namespace mw +} // namespace score diff --git a/score/mw/crypto/api/contexts/src/key_management_context_impl.hpp b/score/mw/crypto/api/contexts/src/key_management_context_impl.hpp new file mode 100644 index 0000000..183e8b9 --- /dev/null +++ b/score/mw/crypto/api/contexts/src/key_management_context_impl.hpp @@ -0,0 +1,141 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_SRC_KEY_MANAGEMENT_CONTEXT_IMPL_HPP +#define SCORE_MW_CRYPTO_API_SRC_KEY_MANAGEMENT_CONTEXT_IMPL_HPP + +#include "score/mw/crypto/api/common/crypto_resource_guard.hpp" +#include "score/mw/crypto/api/common/src/i_release_callback.hpp" +#include "score/mw/crypto/api/common/types.hpp" +#include "score/mw/crypto/api/config/key_operation_params.hpp" +#include "score/mw/crypto/api/contexts/i_key_management_context.hpp" + +#include "score/crypto/api/control_plane/i_connection.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" + +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ +/// @brief Concrete IKeyManagementContext implementation that delegates to the crypto daemon via IPC. +/// +/// Each instance is bound to a daemon-side key management context (identified by +/// context_id) created during construction. Key-producing operations (GenerateKey, +/// etc.) return CryptoResourceGuard instances that auto-release through an +/// IReleaseCallback bound to the daemon connection. +/// +/// Currently implements: +/// - GenerateKey (ephemeral overload) — returns CryptoResourceGuard +/// - Key release via CryptoResourceGuard RAII / explicit Release() +/// +/// Other operations (DeriveKey, AgreeKey, etc.) return kUnsupportedOperation stubs. +class KeyManagementContextImpl final : public IKeyManagementContext +{ + public: + /// @brief Constructs a key management context bound to an existing daemon-side context. + /// @param connection Shared connection for IPC communication + /// @param context_id Daemon-assigned context identifier (from CTX_CREATE response) + KeyManagementContextImpl(std::shared_ptr connection, + uint64_t context_id); + + ~KeyManagementContextImpl() override; + + // No copy, no move. Just handled via unique_ptr + KeyManagementContextImpl(const KeyManagementContextImpl&) = delete; + KeyManagementContextImpl& operator=(const KeyManagementContextImpl&) = delete; + KeyManagementContextImpl(KeyManagementContextImpl&&) = delete; + KeyManagementContextImpl& operator=(KeyManagementContextImpl&&) = delete; + + // -- Key Generation -- + score::Result GenerateKey(const GenerateKeyParams& params) override; + score::Result GenerateKey(const CryptoResourceId& target_slot, + const std::optional& public_slot, + const GenerateKeyParams& params) override; + + // -- Key Derivation (stubs) -- + score::Result DeriveKey(const DeriveKeyParams& params) override; + score::Result DeriveKey(const CryptoResourceId& target_slot, + const DeriveKeyParams& params) override; + + // -- Key Agreement (stubs) -- + score::Result AgreeKey(const AgreeKeyParams& params) override; + score::Result AgreeKey(const CryptoResourceId& target_slot, const AgreeKeyParams& params) override; + + // -- Persistence (stub) -- + score::Result PersistKey(const CryptoResourceId& target_slot, + const CryptoResourceId& ephemeral_key) override; + + // -- Key Unwrapping (stubs) -- + score::Result UnwrapKey(const UnwrapKeyParams& params) override; + score::Result UnwrapKey(const CryptoResourceId& target_slot, + const UnwrapKeyParams& params) override; + + // -- Key Import (stubs) -- + score::Result ImportKey(const ImportKeyParams& params) override; + score::Result ImportKey(const CryptoResourceId& target_slot, + const ImportKeyParams& params) override; + + // -- Key Loading (stub) -- + score::Result LoadKey(const CryptoResourceId& slot) override; + + // -- Key Wrapping (stubs) -- + score::Result WrapKey(const WrapKeyParams& params, score::cpp::span output) override; + score::Result GetWrapKeySize(const WrapKeyParams& params) override; + + // -- Key Export (stubs) -- + score::Result ExportKey(const CryptoResourceId& key, + score::cpp::span output, + FormatType format) override; + score::Result GetExportKeySize(const CryptoResourceId& key, FormatType format) override; + + // -- Key Clearing (stub) -- + score::Result ClearKey(const CryptoResourceId& key) override; + + // -- Slot Queries (stub) -- + score::Result GetKeySlotInfo(const CryptoResourceId& slot) override; + + private: + std::shared_ptr m_connection; + score::crypto::daemon::control_plane::protocol::DataNodeId m_context_id; + + /// @brief IReleaseCallback whose destructor sends CTX_CLOSE to the daemon. + /// Held by the context *and* transitively by every CryptoResourceGuard (via + /// ReleaseCallbackImpl). CTX_CLOSE is therefore deferred until both the + /// context and all outstanding guards have been destroyed. + /// @hint: May look like never used, but used via shared_ptr lifetime handling to keep this context alive until all + /// guards are released. + class ContextReleaseCallbackImpl; + std::shared_ptr m_context_release_callback; + + /// @brief IReleaseCallback implementation for constructing CryptoResourceGuards. + /// Sends KEY_RELEASE for individual keys and holds a shared_ptr to + /// ContextReleaseCallbackImpl to keep the key management context alive. + class ReleaseCallbackImpl; + std::shared_ptr m_release_callback; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_SRC_KEY_MANAGEMENT_CONTEXT_IMPL_HPP diff --git a/score/mw/crypto/api/contexts/src/mac_context_impl.cpp b/score/mw/crypto/api/contexts/src/mac_context_impl.cpp new file mode 100644 index 0000000..7aaee03 --- /dev/null +++ b/score/mw/crypto/api/contexts/src/mac_context_impl.cpp @@ -0,0 +1,309 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/mw/crypto/api/contexts/src/mac_context_impl.hpp" + +#include "score/mw/crypto/api/common/error_domain.hpp" +#include "score/mw/crypto/api/common/types.hpp" + +#include "score/crypto/api/control_plane/i_connection.hpp" +#include "score/crypto/daemon/common/actors.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/daemon/mediator/mediator_operations.hpp" +#include "score/crypto/daemon/provider/handler/operations/mac_handler_operations.hpp" + +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +namespace proto = ::score::crypto::daemon::control_plane::protocol; +namespace actors = ::score::crypto::daemon::common::actors; +namespace mac_ops = ::score::crypto::daemon::provider::handler::mac_handler_operations; + +MacContextImpl::MacContextImpl(std::shared_ptr connection, + uint64_t context_id, + AlgorithmId algorithm) + : m_connection(std::move(connection)), m_context_id(context_id), m_algorithm(algorithm) +{ +} + +MacContextImpl::~MacContextImpl() +{ + if (!m_connection) + { + std::cerr << "[API][MacContextImpl] ERROR: Connection is not initialized during destruction\n"; + return; + } + + auto context_close_res = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation(score::crypto::daemon::mediator::operations::CloseContext()) + .build(); + + if (!context_close_res.has_value()) + { + std::cerr << "[API][MacContextImpl] ERROR: Failed to build CTX_CLOSE request during destruction\n"; + return; + } + + auto response_res = m_connection->SendRequest(context_close_res.value()); + + auto validator = proto::ControlResponseValidator::FromResult(response_res); + validator.expectOperation(score::crypto::daemon::mediator::operations::CloseContext()).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][MacContextImpl] ERROR: CTX_CLOSE response validation failed: " << validator.getError() + << "\n"; + return; + } +} + +score::Result MacContextImpl::Update(score::cpp::span data) +{ + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_MAC_HANDLER, mac_ops::MAC_UPDATE}) + .with_in_data_buffer(data) + .build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][MacContextImpl] ERROR: Failed to build MAC_UPDATE request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "Failed to build MAC_UPDATE request")}; + } + + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_MAC_HANDLER, mac_ops::MAC_UPDATE}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][MacContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "MAC_UPDATE daemon response invalid")}; + } + + return std::monostate{}; +} + +score::Result MacContextImpl::Finalize(score::cpp::span output) +{ + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_MAC_HANDLER, mac_ops::MAC_FINAL}) + .build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][MacContextImpl] ERROR: Failed to build MAC_FINAL request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "Failed to build MAC_FINAL request")}; + } + + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_MAC_HANDLER, mac_ops::MAC_FINAL}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][MacContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "MAC_FINAL daemon response invalid")}; + } + + auto mac_result = validator.getParameterAt(0, 0); + if (!mac_result.has_value()) + { + std::cerr << "[API][MacContextImpl] ERROR: MAC_FINAL response has invalid parameter type\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kOperationFailed, "MAC_FINAL response has invalid parameter type")}; + } + + const auto& mac_data = mac_result.value(); + auto bytes_to_copy = std::min(mac_data.size(), output.size()); + std::memcpy(output.data(), mac_data.data(), bytes_to_copy); + + // TODO: Consider if we should just abort here and not write anything to the output buffer + // We may also be able to get the required out size as soon as we have created the ctx + // at least a sub-set of operations / algorithms. + if (bytes_to_copy < mac_data.size()) + { + std::cerr + << "[API][MacContextImpl] ERROR: Output buffer too small for full MAC tag, truncated copy performed\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kInsufficientBufferSize, + "ERROR: Output buffer too small for full MAC tag, truncated copy performed")}; + } + + return bytes_to_copy; +} + +score::Result MacContextImpl::Verify(score::cpp::span mac) +{ + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_MAC_HANDLER, mac_ops::MAC_VERIFY}) + .with_in_data_buffer(mac) + .build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][MacContextImpl] ERROR: Failed to build MAC_VERIFY request\n"; + return score::Result{score::unexpect, + MakeError(CryptoErrorCode::kOperationFailed, "Failed to build MAC_VERIFY request")}; + } + + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_MAC_HANDLER, mac_ops::MAC_VERIFY}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][MacContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result{score::unexpect, + MakeError(CryptoErrorCode::kOperationFailed, "MAC_VERIFY daemon response invalid")}; + } + + auto verify_result = validator.getParameterAt(0, 0); + if (!verify_result.has_value()) + { + std::cerr << "[API][MacContextImpl] ERROR: MAC_VERIFY response has invalid parameter type\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kOperationFailed, "MAC_VERIFY response has invalid parameter type")}; + } + + return verify_result.value(); +} + +score::Result MacContextImpl::Reset() +{ + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_MAC_HANDLER, mac_ops::MAC_RESET}) + .build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][MacContextImpl] ERROR: Failed to build MAC_RESET request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "Failed to build MAC_RESET request")}; + } + + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_MAC_HANDLER, mac_ops::MAC_RESET}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][MacContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "MAC_RESET daemon response invalid")}; + } + + return std::monostate{}; +} + +std::size_t MacContextImpl::GetMacSize() const noexcept +{ + return GetOutputSize(); +} + +std::size_t MacContextImpl::GetOutputSize() const noexcept +{ + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_MAC_HANDLER, mac_ops::MAC_GET_SIZE}) + .build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][MacContextImpl] ERROR: Failed to build MAC_GET_SIZE request\n"; + return 0; + } + + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_MAC_HANDLER, mac_ops::MAC_GET_SIZE}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][MacContextImpl] ERROR: " << validator.getError() << "\n"; + return 0; + } + + auto size_result = validator.getParameterAt(0, 0); + if (!size_result.has_value()) + { + std::cerr << "[API][MacContextImpl] ERROR: MAC_GET_SIZE response has invalid parameter type\n"; + return 0; + } + + return static_cast(size_result.value()); +} + +score::Result MacContextImpl::Init(std::optional> iv) +{ + if (iv.has_value()) + { + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "Init with IV not yet supported")}; + } + + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_context_id) + .operation({actors::OP_ACTOR_MAC_HANDLER, mac_ops::MAC_INIT}) + .build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][MacContextImpl] ERROR: Failed to build MAC_INIT request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "Failed to build MAC_INIT request")}; + } + + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_MAC_HANDLER, mac_ops::MAC_INIT}).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][MacContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "MAC_INIT daemon response invalid")}; + } + + return std::monostate{}; +} + +} // namespace crypto +} // namespace mw +} // namespace score diff --git a/score/mw/crypto/api/contexts/src/mac_context_impl.hpp b/score/mw/crypto/api/contexts/src/mac_context_impl.hpp new file mode 100644 index 0000000..86a48dd --- /dev/null +++ b/score/mw/crypto/api/contexts/src/mac_context_impl.hpp @@ -0,0 +1,74 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_SRC_MAC_CONTEXT_IMPL_HPP +#define SCORE_MW_CRYPTO_API_SRC_MAC_CONTEXT_IMPL_HPP + +#include "score/mw/crypto/api/contexts/i_mac_context.hpp" + +#include "score/mw/crypto/api/common/types.hpp" + +#include "score/crypto/api/control_plane/i_connection.hpp" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Concrete IMacContext implementation that delegates to the crypto daemon via IPC. +/// +/// Each instance is bound to a daemon-side MAC context (identified by context_id) +/// created during construction. All streaming operations and verification are +/// forwarded to the daemon through the session's IPC connection. +class MacContextImpl final : public IMacContext +{ + public: + /// @brief Constructs a MAC context bound to an existing daemon-side context. + /// @param connection Shared connection for IPC communication + /// @param context_id Daemon-assigned context identifier (from CTX_CREATE response) + /// @param algorithm Algorithm name (e.g., "HMAC-SHA256") for MAC size queries + MacContextImpl(std::shared_ptr connection, + uint64_t context_id, + AlgorithmId algorithm); + + ~MacContextImpl() override; + + // -- IStreamingContext -- + score::Result Init(std::optional> iv) override; + score::Result Update(score::cpp::span data) override; + score::Result Reset() override; + + // -- IStreamingOutputContext -- + score::Result Finalize(score::cpp::span output) override; + std::size_t GetOutputSize() const noexcept override; + + // -- IMacContext -- + score::Result Verify(score::cpp::span mac) override; + std::size_t GetMacSize() const noexcept override; + + private: + std::shared_ptr m_connection; + score::crypto::daemon::control_plane::protocol::DataNodeId m_context_id; + AlgorithmId m_algorithm; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_SRC_MAC_CONTEXT_IMPL_HPP diff --git a/score/mw/crypto/api/crypto_stack_factory.hpp b/score/mw/crypto/api/crypto_stack_factory.hpp new file mode 100644 index 0000000..84e2b7c --- /dev/null +++ b/score/mw/crypto/api/crypto_stack_factory.hpp @@ -0,0 +1,88 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CRYPTO_STACK_FACTORY_HPP +#define SCORE_MW_CRYPTO_API_CRYPTO_STACK_FACTORY_HPP + +#include "score/mw/crypto/api/i_crypto_stack.hpp" +#include "score/result/result.h" + +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Configuration builder for creating a crypto stack instance. +struct CryptoStackConfig +{ + // TODO: Will hide this information in this future, via mw::com + /// @brief Daemon endpoint URI (required). + /// Examples: "unix:///tmp/crypto_daemon.sock" + std::string connection_endpoint{}; + + /// @brief Default per-IPC-call timeout for all operations on this stack. + /// + /// Each IPC call (Init, Update, Finalize, ResolveResource, etc.) must + /// complete within this deadline. For streaming operations, the timeout + /// applies to each individual call, not the entire sequence. + /// + /// When std::nullopt (default), the daemon applies its own default + /// + /// Per-context overrides are available via BaseContextConfig::operation_timeout. + std::optional default_operation_timeout{std::nullopt}; + + /// @brief Set connection endpoint + CryptoStackConfig& SetConnectionEndpoint(std::string_view endpoint) + { + connection_endpoint = std::string{endpoint}; + return *this; + } + + /// @brief Sets the stack-wide default operation timeout. + /// @param timeout Maximum duration for any single IPC call. + /// Operations exceeding this return kOperationTimedOut. + CryptoStackConfig& SetDefaultOperationTimeout(std::chrono::milliseconds timeout) + { + default_operation_timeout = timeout; + return *this; + } + + // Future extensible fields: + // std::optional connection_timeout{std::nullopt}; + // std::optional tls_ca_cert_path{std::nullopt}; + // std::optional max_retry_attempts{std::nullopt}; + // std::optional application_identity_hint{std::nullopt}; +}; + +/// @brief Creates a new crypto stack. +/// +/// Top-level entry point to the crypto middleware. Each call returns a +/// new independent stack handle. Multiple stacks within the same process +/// share the internally managed daemon connection. Contexts and allocators +/// obtained from the stack have independent lifetimes and may outlive it. +/// +/// @param config Stack configuration with at minimum a connection endpoint +/// @return Unique pointer to the created stack, or error on connection failure +score::Result CreateCryptoStack(const CryptoStackConfig& config); + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CRYPTO_STACK_FACTORY_HPP diff --git a/score/mw/crypto/api/future/certificate/BUILD b/score/mw/crypto/api/future/certificate/BUILD new file mode 100644 index 0000000..7f35285 --- /dev/null +++ b/score/mw/crypto/api/future/certificate/BUILD @@ -0,0 +1,31 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +# ============================================================================= +# Certificate support types — not yet active (IPC implementation pending). +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +# cc_library( +# name = "crypto_certificate", +# hdrs = [ +# "cert_types.hpp", +# "i_csr_export.hpp", +# "i_ocsp_request_export.hpp", +# ], +# deps = [ +# "//score/mw/crypto/api/common:crypto_common", +# "@score_baselibs//score/language/futurecpp", +# "@score_baselibs//score/result", +# ], +# ) diff --git a/score/mw/crypto/api/future/certificate/cert_types.hpp b/score/mw/crypto/api/future/certificate/cert_types.hpp new file mode 100644 index 0000000..5978406 --- /dev/null +++ b/score/mw/crypto/api/future/certificate/cert_types.hpp @@ -0,0 +1,55 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CERTIFICATE_CERT_TYPES_HPP +#define SCORE_MW_CRYPTO_API_CERTIFICATE_CERT_TYPES_HPP + +#include "score/mw/crypto/api/common/types.hpp" + +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Result of a certificate verification operation. +enum class CertVerifyResult : uint8_t +{ + kValid, ///< Certificate is valid and trusted + kExpired, ///< Certificate has expired + kNotYetValid, ///< Certificate is not yet valid (future notBefore) + kRevoked, ///< Certificate has been revoked + kNoRootFound, ///< Root CA is not in the trust store + kChainIncomplete, ///< Intermediate certificate missing + kSignatureInvalid, ///< Signature verification failed + kInvalidPurpose, ///< Key usage or extended key usage mismatch + kUnknownAlgorithm, ///< Unsupported or unknown algorithm in certificate + kUnknownError ///< Unspecified verification failure +}; + +/// @brief Status of an OCSP response. +enum class OcspStatus : uint8_t +{ + kGood, ///< Certificate is not revoked + kRevoked, ///< Certificate has been revoked + kUnknown, ///< Responder does not know the certificate + kError ///< OCSP response could not be parsed or verified +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CERTIFICATE_CERT_TYPES_HPP diff --git a/score/mw/crypto/api/future/certificate/i_csr_export.hpp b/score/mw/crypto/api/future/certificate/i_csr_export.hpp new file mode 100644 index 0000000..12f1a73 --- /dev/null +++ b/score/mw/crypto/api/future/certificate/i_csr_export.hpp @@ -0,0 +1,64 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CERTIFICATE_I_CSR_EXPORT_HPP +#define SCORE_MW_CRYPTO_API_CERTIFICATE_I_CSR_EXPORT_HPP + +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Export interface for a generated Certificate Signing Request. +/// +/// Provides access to the DER-encoded CSR data and the submission URL +/// (if configured). The CSR is generated by ICsrGenerationContext::Generate(). +class ICsrExport +{ + public: + using Uptr = std::unique_ptr; + + virtual ~ICsrExport() = default; + + ICsrExport(const ICsrExport&) = delete; + ICsrExport& operator=(const ICsrExport&) = delete; + ICsrExport(ICsrExport&&) = default; + ICsrExport& operator=(ICsrExport&&) = default; + + /// @brief Exports the DER-encoded CSR into the provided buffer. + /// @param output Buffer to receive the encoded CSR + /// @return Number of bytes written on success + virtual score::Result GetEncodedCsr(score::cpp::span output) const = 0; + + /// @brief Returns the CA submission URL, if configured. + /// @return URL string or empty string_view if not configured + virtual std::string_view GetSubmissionUrl() const noexcept = 0; + + protected: + ICsrExport() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CERTIFICATE_I_CSR_EXPORT_HPP diff --git a/score/mw/crypto/api/future/certificate/i_ocsp_request_export.hpp b/score/mw/crypto/api/future/certificate/i_ocsp_request_export.hpp new file mode 100644 index 0000000..1195125 --- /dev/null +++ b/score/mw/crypto/api/future/certificate/i_ocsp_request_export.hpp @@ -0,0 +1,64 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CERTIFICATE_I_OCSP_REQUEST_EXPORT_HPP +#define SCORE_MW_CRYPTO_API_CERTIFICATE_I_OCSP_REQUEST_EXPORT_HPP + +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Export interface for an OCSP request. +/// +/// Provides access to the DER-encoded OCSP request data and the +/// OCSP responder URL. Generated by ICertificateManagementContext::GetOcspRequestData(). +class IOcspRequestExport +{ + public: + using Uptr = std::unique_ptr; + + virtual ~IOcspRequestExport() = default; + + IOcspRequestExport(const IOcspRequestExport&) = delete; + IOcspRequestExport& operator=(const IOcspRequestExport&) = delete; + IOcspRequestExport(IOcspRequestExport&&) = default; + IOcspRequestExport& operator=(IOcspRequestExport&&) = default; + + /// @brief Exports the DER-encoded OCSP request into the provided buffer. + /// @param output Buffer to receive the encoded OCSP request + /// @return Number of bytes written on success + virtual score::Result GetEncodedRequest(score::cpp::span output) const = 0; + + /// @brief Returns the OCSP responder URL. + /// @return URL string or empty string_view if not configured + virtual std::string_view GetResponderUrl() const noexcept = 0; + + protected: + IOcspRequestExport() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CERTIFICATE_I_OCSP_REQUEST_EXPORT_HPP diff --git a/score/mw/crypto/api/future/common/BUILD b/score/mw/crypto/api/future/common/BUILD new file mode 100644 index 0000000..e5d1497 --- /dev/null +++ b/score/mw/crypto/api/future/common/BUILD @@ -0,0 +1,26 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "future_common", + hdrs = [ + "i_secure_storage_manager.hpp", + ], + includes = ["."], + visibility = ["//visibility:public"], + deps = [ + "@score_baselibs//score/language/futurecpp", + "@score_baselibs//score/result", + ], +) diff --git a/score/mw/crypto/api/future/common/i_secure_storage_manager.hpp b/score/mw/crypto/api/future/common/i_secure_storage_manager.hpp new file mode 100644 index 0000000..548c3ec --- /dev/null +++ b/score/mw/crypto/api/future/common/i_secure_storage_manager.hpp @@ -0,0 +1,93 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_FUTURE_COMMON_I_SECURE_STORAGE_MANAGER_HPP +#define SCORE_MW_CRYPTO_API_FUTURE_COMMON_I_SECURE_STORAGE_MANAGER_HPP + +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Future API for application-level secret storage. +/// +/// This interface is planned for a later release and is not part of the +/// current public API surface. It defines AEAD-encrypted per-application +/// storage for secrets, credentials, and other sensitive data. +/// +/// Each application has an isolated namespace — keys are scoped to the +/// calling application's identity (uid). +/// +/// @par Example +/// @code +/// auto storage = stack->GetSecureStorageManager(); +/// if (!storage) return handle_error(); +/// std::vector secret = {0x01, 0x02, 0x03}; +/// storage.value()->Store("my_secret", score::cpp::span{secret}); +/// +/// std::vector output(256); +/// auto bytes = storage.value()->Retrieve("my_secret", score::cpp::span{output}); +/// @endcode +class ISecureStorageManager +{ + public: + using Uptr = std::unique_ptr; + + virtual ~ISecureStorageManager() = default; + + ISecureStorageManager(const ISecureStorageManager&) = delete; + ISecureStorageManager& operator=(const ISecureStorageManager&) = delete; + ISecureStorageManager(ISecureStorageManager&&) = default; + ISecureStorageManager& operator=(ISecureStorageManager&&) = default; + + /// @brief Stores data under a named key, encrypted per-application. + /// @param key Named key to store under (application-scoped) + /// @param data Data to store (will be AEAD-encrypted) + /// @return std::monostate on success, error if storage limit exceeded or key is invalid + virtual score::Result Store(std::string_view key, score::cpp::span data) = 0; + + /// @brief Retrieves and decrypts stored data. + /// @param key Named key to retrieve + /// @param output Buffer to receive the decrypted data + /// @return Number of bytes written on success, error if key not found + virtual score::Result Retrieve(std::string_view key, score::cpp::span output) = 0; + + /// @brief Deletes a stored entry. + /// @param key Named key to delete + /// @return std::monostate on success, error if key not found + virtual score::Result Delete(std::string_view key) = 0; + + /// @brief Lists all stored keys for the current application. + /// @return Vector of key names, or error on failure + virtual score::Result> List() = 0; + + protected: + ISecureStorageManager() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_FUTURE_COMMON_I_SECURE_STORAGE_MANAGER_HPP diff --git a/score/mw/crypto/api/future/config/BUILD b/score/mw/crypto/api/future/config/BUILD new file mode 100644 index 0000000..5128704 --- /dev/null +++ b/score/mw/crypto/api/future/config/BUILD @@ -0,0 +1,35 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +# ============================================================================= +# FUTURE: Config headers below are not yet in scope. To activate, move the +# .hpp back to //score/mw/crypto/api/config/ and add to context_configs. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +# cc_library( +# name = "future_context_configs", +# hdrs = [ +# "aead_context_config.hpp", +# "certificate_context_config.hpp", +# "certificate_verification_context_config.hpp", +# "cipher_context_config.hpp", +# "csr_generation_context_config.hpp", +# "random_context_config.hpp", +# "sign_context_config.hpp", +# "verify_signature_context_config.hpp", +# ], +# deps = [ +# "//score/mw/crypto/api/common:crypto_common", +# ], +# ) diff --git a/score/mw/crypto/api/future/config/aead_context_config.hpp b/score/mw/crypto/api/future/config/aead_context_config.hpp new file mode 100644 index 0000000..e5a900a --- /dev/null +++ b/score/mw/crypto/api/future/config/aead_context_config.hpp @@ -0,0 +1,91 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_AEAD_CONTEXT_CONFIG_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_AEAD_CONTEXT_CONFIG_HPP + +#include "score/mw/crypto/api/common/types.hpp" +#include "score/mw/crypto/api/config/base_context_config.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Configuration for AEAD context creation. +/// +/// Requires an algorithm, key, and cipher direction. Typical algorithms +/// include AES-128-GCM, AES-256-GCM, ChaCha20-Poly1305. +/// +/// @par Example +/// @code +/// AeadContextConfig config; +/// config.SetAlgorithm("AES-256-GCM") +/// .SetKey(aead_key) +/// .SetDirection(CipherDirection::kEncrypt); +/// auto ctx = crypto_context->CreateAeadContext(config); +/// @endcode +struct AeadContextConfig : public BaseContextConfig +{ + /// @brief Handle to the AEAD key (required). + /// Must be a CryptoResourceId with type == kKey. + CryptoResourceId key{}; + + /// @brief Cipher direction: encrypt or decrypt (required). + CipherDirection direction{CipherDirection::kEncrypt}; + + // -- Fluent builder -- + + AeadContextConfig& SetAlgorithm(const AlgorithmId& alg) noexcept + { + BaseContextConfig::SetAlgorithm(alg); + return *this; + } + + AeadContextConfig& SetKey(const CryptoResourceId& k) noexcept + { + key = k; + return *this; + } + + AeadContextConfig& SetDirection(CipherDirection dir) noexcept + { + direction = dir; + return *this; + } + + AeadContextConfig& SetProvider(const CryptoResourceId& prov) noexcept + { + BaseContextConfig::SetProvider(prov); + return *this; + } + + AeadContextConfig& SetProviderType(ProviderType type) noexcept + { + BaseContextConfig::SetProviderType(type); + return *this; + } + + AeadContextConfig& SetExtendedParameter(const std::string& key, const std::string& value) + { + BaseContextConfig::SetExtendedParameter(key, value); + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_AEAD_CONTEXT_CONFIG_HPP diff --git a/score/mw/crypto/api/future/config/certificate_context_config.hpp b/score/mw/crypto/api/future/config/certificate_context_config.hpp new file mode 100644 index 0000000..01bfe1c --- /dev/null +++ b/score/mw/crypto/api/future/config/certificate_context_config.hpp @@ -0,0 +1,70 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_CERTIFICATE_CONTEXT_CONFIG_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_CERTIFICATE_CONTEXT_CONFIG_HPP + +#include "score/mw/crypto/api/config/base_context_config.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Configuration for certificate management context creation. +/// +/// Provider is optional — certificate operations use the provider +/// associated with each certificate's storage or the signing key's +/// primary_provider as appropriate. Setting a provider explicitly +/// scopes all certificate operations to that provider. +/// +/// @par Example +/// @code +/// CertificateContextConfig config; +/// auto ctx = crypto_context->CreateCertificateManagementContext(config); +/// @endcode +struct CertificateContextConfig : public BaseContextConfig +{ + // -- Fluent builder -- + + CertificateContextConfig& SetAlgorithm(const AlgorithmId& alg) noexcept + { + BaseContextConfig::SetAlgorithm(alg); + return *this; + } + + CertificateContextConfig& SetProvider(const CryptoResourceId& prov) noexcept + { + BaseContextConfig::SetProvider(prov); + return *this; + } + + CertificateContextConfig& SetProviderType(ProviderType type) noexcept + { + BaseContextConfig::SetProviderType(type); + return *this; + } + + CertificateContextConfig& SetExtendedParameter(const std::string& key, const std::string& value) + { + BaseContextConfig::SetExtendedParameter(key, value); + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_CERTIFICATE_CONTEXT_CONFIG_HPP diff --git a/score/mw/crypto/api/future/config/certificate_verification_context_config.hpp b/score/mw/crypto/api/future/config/certificate_verification_context_config.hpp new file mode 100644 index 0000000..67ef135 --- /dev/null +++ b/score/mw/crypto/api/future/config/certificate_verification_context_config.hpp @@ -0,0 +1,89 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_CERTIFICATE_VERIFICATION_CONTEXT_CONFIG_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_CERTIFICATE_VERIFICATION_CONTEXT_CONFIG_HPP + +#include "score/mw/crypto/api/common/types.hpp" +#include "score/mw/crypto/api/config/base_context_config.hpp" + +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Configuration for certificate verification context creation. +/// +/// Provider is optional — certificate verification uses the provider +/// associated with the certificate's signing key. Setting a provider +/// explicitly scopes the verification to that provider. +/// +/// An optional default RevocationCheckPolicy can be set at config time; +/// it can be overridden per-verification via SetRevocationCheckPolicy() +/// on the context itself. +/// +/// @par Example +/// @code +/// CertificateVerificationContextConfig config; +/// config.SetRevocationPolicy(RevocationCheckPolicy::kOcspWithCrlFallback); +/// auto ctx = crypto_context->CreateCertificateVerificationContext(config); +/// ctx->SetCertificate(cert); +/// ctx->SetVerificationTrustStore(trust_anchor); +/// auto result = ctx->Verify(); +/// @endcode +struct CertificateVerificationContextConfig : public BaseContextConfig +{ + /// @brief Default revocation check policy for verifications using this context. + std::optional revocation_policy{std::nullopt}; + + // -- Fluent builder -- + + CertificateVerificationContextConfig& SetAlgorithm(const AlgorithmId& alg) noexcept + { + BaseContextConfig::SetAlgorithm(alg); + return *this; + } + + CertificateVerificationContextConfig& SetProvider(const CryptoResourceId& prov) noexcept + { + BaseContextConfig::SetProvider(prov); + return *this; + } + + CertificateVerificationContextConfig& SetProviderType(ProviderType type) noexcept + { + BaseContextConfig::SetProviderType(type); + return *this; + } + + CertificateVerificationContextConfig& SetRevocationPolicy(RevocationCheckPolicy policy) noexcept + { + revocation_policy = policy; + return *this; + } + + CertificateVerificationContextConfig& SetExtendedParameter(const std::string& key, const std::string& value) + { + BaseContextConfig::SetExtendedParameter(key, value); + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_CERTIFICATE_VERIFICATION_CONTEXT_CONFIG_HPP diff --git a/score/mw/crypto/api/future/config/cipher_context_config.hpp b/score/mw/crypto/api/future/config/cipher_context_config.hpp new file mode 100644 index 0000000..008ed6b --- /dev/null +++ b/score/mw/crypto/api/future/config/cipher_context_config.hpp @@ -0,0 +1,103 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_CIPHER_CONTEXT_CONFIG_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_CIPHER_CONTEXT_CONFIG_HPP + +#include "score/mw/crypto/api/common/crypto_resource_guard.hpp" +#include "score/mw/crypto/api/common/types.hpp" +#include "score/mw/crypto/api/config/base_context_config.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Configuration for cipher context creation (encryption or decryption). +/// +/// Requires an algorithm, a key, and a cipher direction. Typical algorithms +/// include AES-128-CBC, AES-256-CBC, AES-256-CTR, ChaCha20. +/// When provider is omitted, the daemon auto-resolves from the key's +/// underlying primary_provider. +/// +/// @par Example — encryption +/// @code +/// CipherContextConfig config; +/// config.SetAlgorithm("AES-256-CBC") +/// .SetKey(key_id) +/// .SetDirection(CipherDirection::kEncrypt); +/// auto ctx = crypto_context->CreateCipherContext(config); +/// @endcode +/// +/// @par Example — decryption +/// @code +/// CipherContextConfig config; +/// config.SetAlgorithm("AES-256-CBC") +/// .SetKey(key_id) +/// .SetDirection(CipherDirection::kDecrypt); +/// auto ctx = crypto_context->CreateCipherContext(config); +/// @endcode +struct CipherContextConfig : public BaseContextConfig +{ + /// @brief Handle to the cipher key (required). + /// Must be a CryptoResourceId with type == kKey. + CryptoResourceId key{}; + + /// @brief Cipher direction: encrypt or decrypt (required). + CipherDirection direction{CipherDirection::kEncrypt}; + + // -- Fluent builder -- + + CipherContextConfig& SetAlgorithm(const AlgorithmId& alg) noexcept + { + BaseContextConfig::SetAlgorithm(alg); + return *this; + } + + CipherContextConfig& SetKey(const CryptoResourceId& k) noexcept + { + key = k; + return *this; + } + + CipherContextConfig& SetDirection(CipherDirection dir) noexcept + { + direction = dir; + return *this; + } + + CipherContextConfig& SetProvider(const CryptoResourceId& prov) noexcept + { + BaseContextConfig::SetProvider(prov); + return *this; + } + + CipherContextConfig& SetProviderType(ProviderType type) noexcept + { + BaseContextConfig::SetProviderType(type); + return *this; + } + + CipherContextConfig& SetExtendedParameter(const std::string& key, const std::string& value) + { + BaseContextConfig::SetExtendedParameter(key, value); + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_CIPHER_CONTEXT_CONFIG_HPP diff --git a/score/mw/crypto/api/future/config/csr_generation_context_config.hpp b/score/mw/crypto/api/future/config/csr_generation_context_config.hpp new file mode 100644 index 0000000..ae67ca6 --- /dev/null +++ b/score/mw/crypto/api/future/config/csr_generation_context_config.hpp @@ -0,0 +1,73 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_CSR_GENERATION_CONTEXT_CONFIG_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_CSR_GENERATION_CONTEXT_CONFIG_HPP + +#include "score/mw/crypto/api/config/base_context_config.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Configuration for CSR generation context creation. +/// +/// Provider is optional — CSR generation uses the provider associated +/// with the signing key's primary_provider. Setting a provider explicitly +/// scopes the CSR generation to that provider. +/// +/// @par Example +/// @code +/// CsrGenerationContextConfig config; +/// auto ctx = crypto_context->CreateCsrGenerationContext(config); +/// ctx->SetSubjectKey(signing_key); +/// ctx->SetSignatureAlgorithm("ML-DSA-65"); +/// ctx->SetSubjectDn("CN=MyDevice,O=Corp,C=DE"); +/// auto csr = ctx->Generate(); +/// @endcode +struct CsrGenerationContextConfig : public BaseContextConfig +{ + // -- Fluent builder -- + + CsrGenerationContextConfig& SetAlgorithm(const AlgorithmId& alg) noexcept + { + BaseContextConfig::SetAlgorithm(alg); + return *this; + } + + CsrGenerationContextConfig& SetProvider(const CryptoResourceId& prov) noexcept + { + BaseContextConfig::SetProvider(prov); + return *this; + } + + CsrGenerationContextConfig& SetProviderType(ProviderType type) noexcept + { + BaseContextConfig::SetProviderType(type); + return *this; + } + + CsrGenerationContextConfig& SetExtendedParameter(const std::string& key, const std::string& value) + { + BaseContextConfig::SetExtendedParameter(key, value); + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_CSR_GENERATION_CONTEXT_CONFIG_HPP diff --git a/score/mw/crypto/api/future/config/random_context_config.hpp b/score/mw/crypto/api/future/config/random_context_config.hpp new file mode 100644 index 0000000..8912465 --- /dev/null +++ b/score/mw/crypto/api/future/config/random_context_config.hpp @@ -0,0 +1,70 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_RANDOM_CONTEXT_CONFIG_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_RANDOM_CONTEXT_CONFIG_HPP + +#include "score/mw/crypto/api/config/base_context_config.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Configuration for random number generation context creation. +/// +/// Algorithm is optional — when omitted, the daemon selects the default +/// RNG source. Provider preference can be used to request hardware +/// entropy sources (e.g., TRNG) when available. +/// +/// @par Example +/// @code +/// RandomContextConfig config; +/// config.SetProviderType(ProviderType::kHardwarePreferred); +/// auto ctx = crypto_context->CreateRandomContext(config); +/// @endcode +struct RandomContextConfig : public BaseContextConfig +{ + // -- Fluent builder -- + + RandomContextConfig& SetAlgorithm(const AlgorithmId& alg) noexcept + { + BaseContextConfig::SetAlgorithm(alg); + return *this; + } + + RandomContextConfig& SetProvider(const CryptoResourceId& prov) noexcept + { + BaseContextConfig::SetProvider(prov); + return *this; + } + + RandomContextConfig& SetProviderType(ProviderType type) noexcept + { + BaseContextConfig::SetProviderType(type); + return *this; + } + + RandomContextConfig& SetExtendedParameter(const std::string& key, const std::string& value) + { + BaseContextConfig::SetExtendedParameter(key, value); + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_RANDOM_CONTEXT_CONFIG_HPP diff --git a/score/mw/crypto/api/future/config/sign_context_config.hpp b/score/mw/crypto/api/future/config/sign_context_config.hpp new file mode 100644 index 0000000..ba17f95 --- /dev/null +++ b/score/mw/crypto/api/future/config/sign_context_config.hpp @@ -0,0 +1,79 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_SIGN_CONTEXT_CONFIG_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_SIGN_CONTEXT_CONFIG_HPP + +#include "score/mw/crypto/api/config/base_context_config.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Configuration for signature generation context creation. +/// +/// Requires an algorithm and a private key. Supports classical +/// algorithms (RSA-PSS, ECDSA) and PQC algorithms (ML-DSA-65, SLH-DSA-SHA2-128s). +/// +/// @par Example +/// @code +/// SignContextConfig config; +/// config.SetAlgorithm("ML-DSA-65").SetKey(signing_key); +/// auto ctx = crypto_context->CreateSignContext(config); +/// @endcode +struct SignContextConfig : public BaseContextConfig +{ + /// @brief Handle to the private signing key (required). + /// Must be a CryptoResourceId with type == kKey. + CryptoResourceId key{}; + + // -- Fluent builder -- + + SignContextConfig& SetAlgorithm(const AlgorithmId& alg) noexcept + { + BaseContextConfig::SetAlgorithm(alg); + return *this; + } + + SignContextConfig& SetKey(const CryptoResourceId& k) noexcept + { + key = k; + return *this; + } + + SignContextConfig& SetProvider(const CryptoResourceId& prov) noexcept + { + BaseContextConfig::SetProvider(prov); + return *this; + } + + SignContextConfig& SetProviderType(ProviderType type) noexcept + { + BaseContextConfig::SetProviderType(type); + return *this; + } + + SignContextConfig& SetExtendedParameter(const std::string& key, const std::string& value) + { + BaseContextConfig::SetExtendedParameter(key, value); + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_SIGN_CONTEXT_CONFIG_HPP diff --git a/score/mw/crypto/api/future/config/verify_signature_context_config.hpp b/score/mw/crypto/api/future/config/verify_signature_context_config.hpp new file mode 100644 index 0000000..c57c3cd --- /dev/null +++ b/score/mw/crypto/api/future/config/verify_signature_context_config.hpp @@ -0,0 +1,79 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONFIG_VERIFY_SIGNATURE_CONTEXT_CONFIG_HPP +#define SCORE_MW_CRYPTO_API_CONFIG_VERIFY_SIGNATURE_CONTEXT_CONFIG_HPP + +#include "score/mw/crypto/api/config/base_context_config.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Configuration for signature verification context creation. +/// +/// Requires an algorithm and a public key. Supports classical +/// algorithms (RSA-PSS, ECDSA) and PQC algorithms (ML-DSA-65, SLH-DSA-SHA2-128s). +/// +/// @par Example +/// @code +/// VerifySignatureContextConfig config; +/// config.SetAlgorithm("ML-DSA-65").SetKey(public_key); +/// auto ctx = crypto_context->CreateVerifySignatureContext(config); +/// @endcode +struct VerifySignatureContextConfig : public BaseContextConfig +{ + /// @brief Handle to the public verification key (required). + /// Must be a CryptoResourceId with type == kKey. + CryptoResourceId key{}; + + // -- Fluent builder -- + + VerifySignatureContextConfig& SetAlgorithm(const AlgorithmId& alg) noexcept + { + BaseContextConfig::SetAlgorithm(alg); + return *this; + } + + VerifySignatureContextConfig& SetKey(const CryptoResourceId& k) noexcept + { + key = k; + return *this; + } + + VerifySignatureContextConfig& SetProvider(const CryptoResourceId& prov) noexcept + { + BaseContextConfig::SetProvider(prov); + return *this; + } + + VerifySignatureContextConfig& SetProviderType(ProviderType type) noexcept + { + BaseContextConfig::SetProviderType(type); + return *this; + } + + VerifySignatureContextConfig& SetExtendedParameter(const std::string& key, const std::string& value) + { + BaseContextConfig::SetExtendedParameter(key, value); + return *this; + } +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONFIG_VERIFY_SIGNATURE_CONTEXT_CONFIG_HPP diff --git a/score/mw/crypto/api/future/contexts/BUILD b/score/mw/crypto/api/future/contexts/BUILD new file mode 100644 index 0000000..724017c --- /dev/null +++ b/score/mw/crypto/api/future/contexts/BUILD @@ -0,0 +1,78 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +# ============================================================================= +# Context interfaces below are not yet active (IPC implementation pending). +# Each is defined in this directory and declared in i_crypto_context.hpp. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +# -- Cipher (symmetric encrypt/decrypt) -- +# cc_library( +# name = "cipher_context", +# hdrs = ["i_cipher_context.hpp"], +# deps = [ +# "//score/mw/crypto/api/contexts:context_bases", +# "//score/mw/crypto/api/common:crypto_common", +# "@score_baselibs//score/result", +# ], +# ) + +# -- Sign / Verify -- +# cc_library( +# name = "sign_contexts", +# hdrs = ["i_sign_context.hpp", "i_verify_signature_context.hpp"], +# deps = [ +# "//score/mw/crypto/api/contexts:context_bases", +# "//score/mw/crypto/api/common:crypto_common", +# "@score_baselibs//score/result", +# ], +# ) + +# -- AEAD -- +# cc_library( +# name = "aead_context", +# hdrs = ["i_aead_context.hpp"], +# deps = [ +# "//score/mw/crypto/api/contexts:context_bases", +# "//score/mw/crypto/api/common:crypto_common", +# "@score_baselibs//score/result", +# ], +# ) + +# -- Random -- +# cc_library( +# name = "random_context", +# hdrs = ["i_random_context.hpp"], +# deps = [ +# "//score/mw/crypto/api/contexts:context_bases", +# "//score/mw/crypto/api/common:crypto_common", +# "@score_baselibs//score/result", +# ], +# ) + +# -- Certificate management + verification + CSR -- +# cc_library( +# name = "certificate_contexts", +# hdrs = [ +# "i_certificate_management_context.hpp", +# "i_certificate_verification_context.hpp", +# "i_csr_generation_context.hpp", +# ], +# deps = [ +# "//score/mw/crypto/api/contexts:context_bases", +# "//score/mw/crypto/api/common:crypto_common", +# "//score/mw/crypto/api/future/certificate:crypto_certificate", +# "@score_baselibs//score/result", +# ], +# ) diff --git a/score/mw/crypto/api/future/contexts/i_aead_context.hpp b/score/mw/crypto/api/future/contexts/i_aead_context.hpp new file mode 100644 index 0000000..3921e73 --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_aead_context.hpp @@ -0,0 +1,104 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_AEAD_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_AEAD_CONTEXT_HPP + +#include "score/mw/crypto/api/contexts/i_streaming_context.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Interface for authenticated encryption with associated data (AEAD). +/// +/// Unified context — direction (encrypt/decrypt) is set via AeadContextConfig. +/// Supports streaming: Init(iv) → UpdateAad* → Update* → Finalize/VerifyAndFinalize. +/// +/// Exposes Init() and Reset() from the base class. Init() uses the base +/// optional-IV signature; a nonce/IV must be provided — passing nullopt +/// returns kUnsupportedOperation. Update() and Finalize() use AEAD-specific +/// signatures with separate tag output. +/// +/// Compatible with AES-GCM, AES-CCM, ChaCha20-Poly1305, and future +/// PQC-hybrid AEAD constructions. +/// +/// @note Derives from IStreamingContext (not IStreamingOutputContext) because +/// AEAD has dual finalization paths (Finalize for encrypt, VerifyAndFinalize +/// for decrypt) with different semantics. +class IAeadContext : public IStreamingContext +{ + public: + using Uptr = std::unique_ptr; + + ~IAeadContext() override = default; + + IAeadContext(const IAeadContext&) = delete; + IAeadContext& operator=(const IAeadContext&) = delete; + IAeadContext(IAeadContext&&) = default; + IAeadContext& operator=(IAeadContext&&) = default; + + // -- Streaming API (from base classes) -- + using IStreamingContext::Init; + using IStreamingContext::Reset; + + /// @brief Feeds additional authenticated data (AAD). + /// @param aad Associated data to authenticate (not encrypted) + /// @return std::monostate on success, error on failure + /// @note Must be called after Init() and before Update(). Can be called + /// multiple times. All AAD must be fed before any plaintext/ciphertext. + virtual score::Result UpdateAad(score::cpp::span aad) = 0; + + /// @brief Processes input data (plaintext or ciphertext depending on direction). + /// @param input Input data to encrypt or decrypt + /// @param output Output buffer for the result + /// @return Number of output bytes written on success, error on failure + virtual score::Result Update(score::cpp::span input, + score::cpp::span output) = 0; + + /// @brief Finalizes encryption, producing remaining output and the auth tag. + /// @param output Buffer for any remaining ciphertext + /// @param tag_out Buffer to receive the authentication tag + /// @return Number of remaining output bytes written on success, error on failure + /// @note Only valid in encrypt mode (CipherDirection::kEncrypt in config). + virtual score::Result Finalize(score::cpp::span output, + score::cpp::span tag_out) = 0; + + /// @brief Finalizes decryption, verifying the authentication tag. + /// @param output Buffer for any remaining plaintext + /// @param tag_in The authentication tag to verify + /// @return true if tag verification succeeds, error on failure or tag mismatch + /// @note Only valid in decrypt mode (CipherDirection::kDecrypt in config). + /// If verification fails, no plaintext is produced (output is wiped). + virtual score::Result VerifyAndFinalize(score::cpp::span output, + score::cpp::span tag_in) = 0; + + /// @brief Returns the authentication tag size in bytes for the configured algorithm. + virtual std::size_t GetTagSize() const noexcept = 0; + + protected: + IAeadContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_AEAD_CONTEXT_HPP diff --git a/score/mw/crypto/api/future/contexts/i_certificate_management_context.hpp b/score/mw/crypto/api/future/contexts/i_certificate_management_context.hpp new file mode 100644 index 0000000..26e6367 --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_certificate_management_context.hpp @@ -0,0 +1,246 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_FUTURE_CONTEXTS_I_CERTIFICATE_MANAGEMENT_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_FUTURE_CONTEXTS_I_CERTIFICATE_MANAGEMENT_CONTEXT_HPP + +#include "score/mw/crypto/api/certificate/i_ocsp_request_export.hpp" +#include "score/mw/crypto/api/common/crypto_resource_guard.hpp" +#include "score/mw/crypto/api/common/types.hpp" +#include "score/mw/crypto/api/contexts/i_context.hpp" +#include "score/mw/crypto/api/future/objects/i_certificate_object.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Interface for certificate lifecycle management operations. +/// +/// Mirrors the structure of IKeyManagementContext for certificates: +/// - **Parse** raw bytes into a daemon-backed ICertificateObject with an +/// ephemeral resource ID. +/// - **SaveCertificate** copies an ephemeral certificate to a persistent slot +/// (copy semantics — the ephemeral cert remains valid until the +/// ICertificateObject goes out of scope). +/// - **Export / convert** using a two-call pattern: query the required buffer +/// size first, then fill the caller-supplied span. +/// - **Slot management**, **CRL management**, and **trust store management** +/// are all co-located here. +/// +/// **ParseCertificate lifecycle**: +/// @code +/// auto cert = cert_mgmt->ParseCertificate(der_bytes, FormatType::kDer).value(); +/// // cert->GetId() is valid — daemon assigned an ephemeral kCertificate ID. +/// // Inspect before committing: +/// if (cert->GetNotAfter() < current_time) { return Error; } +/// cert_mgmt->SaveCertificate(cert->GetId(), target_slot).value(); +/// // cert goes out of scope → destructor → daemon releases ephemeral copy. +/// @endcode +class ICertificateManagementContext : public IContext +{ + public: + using Uptr = std::unique_ptr; + + ~ICertificateManagementContext() override = default; + + ICertificateManagementContext(const ICertificateManagementContext&) = delete; + ICertificateManagementContext& operator=(const ICertificateManagementContext&) = delete; + ICertificateManagementContext(ICertificateManagementContext&&) = default; + ICertificateManagementContext& operator=(ICertificateManagementContext&&) = default; + + // ---- Parsing ---- + + /// @brief Parses a single X.509 certificate from encoded data. + /// + /// Sends the raw bytes to the daemon, which validates the certificate + /// structure and assigns an ephemeral kCertificate resource ID. + /// The returned ICertificateObject::GetId() is immediately valid. + /// + /// @param cert_data DER or PEM encoded certificate bytes + /// @param format Encoding format of the input data + /// @return ICertificateObject with a daemon-assigned ephemeral ID. + /// Destroying the object releases the ephemeral ID. + virtual score::Result ParseCertificate(score::cpp::span cert_data, + FormatType format) = 0; + + /// @brief Parses multiple certificates from a PEM bundle or DER chain. + /// + /// @param cert_data PEM bundle or concatenated certificate data + /// @param format Encoding format of the data + /// @return Ordered vector of ICertificateObject (first = leaf/first in bundle). + /// Each object has a daemon-assigned ephemeral ID. + virtual score::Result> ParseCertificates( + score::cpp::span cert_data, + FormatType format) = 0; + + // ---- Persistence ---- + + /// @brief Copies an ephemeral certificate to a persistent certificate slot. + /// + /// Copy semantics: the ephemeral certificate (and its ICertificateObject) + /// remains valid after this call. The object releases the ephemeral copy + /// independently when it is destroyed. + /// + /// Typical usage: parse → inspect fields → save to slot. + /// + /// @param cert CryptoResourceId of the certificate to save (type = kCertificate). + /// Pass the result of ICertificateObject::GetId() directly. + /// @param target_slot Handle to the target slot (type = kCertSlot) + /// @return std::monostate on success, error if slot is occupied or access is denied + virtual score::Result SaveCertificate(const CryptoResourceId& cert, + const CryptoResourceId& target_slot) = 0; + + // ---- Export ---- + + /// @brief Returns the encoded size of a certificate in the requested format. + /// + /// Call this before ExportCertificate() to allocate a correctly-sized buffer. + /// + /// @param cert Handle to the certificate (type = kCertificate) + /// @param format Desired output encoding (DER or PEM) + /// @return Required buffer size in bytes, or error on failure + virtual score::Result GetCertificateExportSize(const CryptoResourceId& cert, FormatType format) = 0; + + /// @brief Exports a certificate in the requested encoding format. + /// + /// Call GetCertificateExportSize() first to determine the buffer size. + /// + /// @param cert Handle to the certificate (type = kCertificate or kCertSlot) + /// @param format Desired output encoding (DER or PEM) + /// @param output Caller-supplied buffer; must be at least GetCertificateExportSize() bytes + /// @return Number of bytes written, or error if handle is invalid or buffer too small + virtual score::Result ExportCertificate(const CryptoResourceId& cert, + FormatType format, + score::cpp::span output) = 0; + + // ---- Format conversion ---- + + /// @brief Returns the size of the certificate data after format conversion. + /// + /// Call this before ConvertCertificateFormat() to allocate a correctly-sized buffer. + /// + /// @param input Certificate data in the source format + /// @param input_format Format of the input data + /// @param output_format Desired output format + /// @return Required buffer size in bytes + virtual score::Result GetConvertedCertificateSize(score::cpp::span input, + FormatType input_format, + FormatType output_format) = 0; + + /// @brief Converts certificate data between DER and PEM formats. + /// + /// Call GetConvertedCertificateSize() first to allocate the output buffer. + /// + /// @param input Certificate data in the source format + /// @param input_format Format of the input data + /// @param output_format Desired output format + /// @param output Caller-supplied buffer; must be at least GetConvertedCertificateSize() bytes + /// @return Number of bytes written + virtual score::Result ConvertCertificateFormat(score::cpp::span input, + FormatType input_format, + FormatType output_format, + score::cpp::span output) = 0; + + // ---- Slot management ---- + + /// @brief Clears a persistent certificate slot, erasing its contents. + /// @param slot Handle to the slot to clear (type = kCertSlot) + /// @return std::monostate on success, error if the slot is not found or access is denied + virtual score::Result ClearCertificate(const CryptoResourceId& slot) = 0; + + /// @brief Queries the occupancy and metadata of a certificate slot. + /// @param slot Handle to the slot (type = kCertSlot) + /// @return CertificateSlotInfo with occupancy, algorithm, and provider binding + virtual score::Result GetCertificateSlotInfo(const CryptoResourceId& slot) = 0; + + // ---- CRL management ---- + + /// @brief Imports a Certificate Revocation List and associates it with its issuer. + /// + /// The returned CryptoResourceId uses ResourceType::kCrl and carries the + /// same numeric id as the resolved issuer certificate — callers can look up + /// the applicable CRL for any issuer by re-resolving with ResourceType::kCrl. + /// + /// @param crl_data Encoded CRL data + /// @param format Encoding format of the CRL + /// @param issuer_cert Handle to the issuer certificate + /// (type = kCertificate or kCertSlot). The daemon validates that the + /// CRL issuer DN matches and that the CRL signature is correct. + /// @param persist When true, the CRL survives the guard's destructor + /// (kPersistent — daemon retains the CRL when the client releases its + /// reference). When false, the CRL is deleted when the guard is destroyed. + /// @return Guard wrapping a handle with type = kCrl. Pass guard.GetId() to + /// SetCrl() or re-resolve via ResourceType::kCrl on the issuer id. + virtual score::Result ImportCrl(score::cpp::span crl_data, + FormatType format, + const CryptoResourceId& issuer_cert, + bool persist) = 0; + + /// @brief Removes a specific CRL from the store. + /// @param crl Handle to the CRL to delete (type = kCrl) + /// @return std::monostate on success, error if the CRL is not found + virtual score::Result DeleteCrl(const CryptoResourceId& crl) = 0; + + /// @brief Bulk-deletes expired CRLs from the store. + /// @return Number of CRLs deleted + virtual score::Result DeleteExpiredCrls() = 0; + + /// @brief Bulk-deletes expired certificates from persistent slots. + /// @return Number of certificates deleted + virtual score::Result DeleteExpiredCertificates() = 0; + + // ---- Key extraction and OCSP ---- + + /// @brief Extracts the public key from a certificate as an ephemeral key resource. + /// + /// The returned CryptoResourceGuard owns an ephemeral kKey resource. It can + /// be passed directly to any API accepting `const CryptoResourceId&` via + /// implicit conversion. The daemon releases the key when the guard is destroyed. + /// + /// @param cert Handle to the certificate (type = kCertificate or kCertSlot) + /// @return Pair of CryptoResourceGuard (ephemeral key) and its AlgorithmId + /// (e.g., "RSA-2048", "ECDSA-P256", "ML-DSA-65") + virtual score::Result> LoadCertificatePublicKey( + const CryptoResourceId& cert) = 0; + + /// @brief Constructs an OCSP request for a certificate's revocation status. + /// + /// The returned object exposes the DER-encoded OCSP request and the responder URL. + /// Send the request to the URL via HTTP POST, then feed the response to + /// ICertificateVerificationContext::SetOcspResponse(). + /// + /// @param cert Handle to the certificate to check (type = kCertificate or kCertSlot) + /// @param issuer_cert Handle to the issuer certificate (required for OCSP request construction) + /// @return Export object providing the encoded request and responder URL + virtual score::Result GetOcspRequestData(const CryptoResourceId& cert, + const CryptoResourceId& issuer_cert) = 0; + + protected: + ICertificateManagementContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_FUTURE_CONTEXTS_I_CERTIFICATE_MANAGEMENT_CONTEXT_HPP diff --git a/score/mw/crypto/api/future/contexts/i_certificate_verification_context.hpp b/score/mw/crypto/api/future/contexts/i_certificate_verification_context.hpp new file mode 100644 index 0000000..e369387 --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_certificate_verification_context.hpp @@ -0,0 +1,155 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_CERTIFICATE_VERIFICATION_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_CERTIFICATE_VERIFICATION_CONTEXT_HPP + +#include "score/mw/crypto/api/certificate/cert_types.hpp" +#include "score/mw/crypto/api/common/types.hpp" +#include "score/mw/crypto/api/contexts/i_context.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Builder-style context for certificate and chain verification. +/// +/// Created via ICryptoContext::CreateCertificateVerificationContext(). +/// Follows the same create-configure-execute pattern as other contexts +/// for uniformity. Configure the verification parameters via setter +/// methods, then call Verify() to execute. +/// +/// @par Example — single certificate verification +/// @code +/// auto ctx = crypto_context->CreateCertificateVerificationContext(config).value(); +/// ctx->SetCertificate(leaf_cert); +/// ctx->SetVerificationTrustStore(system_trust_store); +/// ctx->SetRevocationCheckPolicy(RevocationCheckPolicy::kOcspWithCrlFallback); +/// ctx->SetOcspResponse(ocsp_response_data); +/// auto result = ctx->Verify(); +/// @endcode +/// +/// @par Example — chain verification with additional trust anchors +/// @code +/// // ext_ca is a kCertificate from ParseCertificate() — not persisted. +/// std::array extra = {ext_ca->GetId()}; +/// auto ctx = crypto_context->CreateCertificateVerificationContext(config).value(); +/// ctx->SetCertificateChain(chain); +/// ctx->SetVerificationTrustStore(system_trust_store); +/// ctx->SetAdditionalTrustAnchors(extra); // local to this context, no system store change +/// auto result = ctx->Verify(); +/// @endcode +class ICertificateVerificationContext : public IContext +{ + public: + using Uptr = std::unique_ptr; + + ~ICertificateVerificationContext() override = default; + + ICertificateVerificationContext(const ICertificateVerificationContext&) = delete; + ICertificateVerificationContext& operator=(const ICertificateVerificationContext&) = delete; + ICertificateVerificationContext(ICertificateVerificationContext&&) = default; + ICertificateVerificationContext& operator=(ICertificateVerificationContext&&) = default; + + // ---- Configuration setters (call before Verify()) ---- + + /// @brief Sets the leaf certificate to verify. + /// @param cert Handle to the certificate to verify + /// @return std::monostate on success, error if cert handle is invalid + /// @note Mutually exclusive with SetCertificateChain(). + virtual score::Result SetCertificate(const CryptoResourceId& cert) = 0; + + /// @brief Sets a certificate chain to verify (leaf first). + /// @param chain Ordered chain of certificate handles (leaf first, root last) + /// @return std::monostate on success, error if any handle is invalid + /// @note Mutually exclusive with SetCertificate(). + virtual score::Result SetCertificateChain(score::cpp::span chain) = 0; + + /// @brief Sets the system trust store to use for certificate chain verification. + /// + /// The trust store is a manifest-configured named group of persistent certificate + /// slots. Resolve it by name with ResourceType::kVerificationTrustStore. + /// Empty slots in the store are silently skipped at verification time. + /// + /// @param trust_store Handle to the verification trust store + /// (type = kVerificationTrustStore) + /// @return std::monostate on success, error if handle is invalid + virtual score::Result SetVerificationTrustStore(const CryptoResourceId& trust_store) = 0; + + /// @brief Supplies additional trust anchors for this verification, beyond the system trust store. + /// + /// Use this to trust certificates not provisioned in any system trust store — + /// e.g. an external CA received at runtime, a rotation candidate, or a test root. + /// Does not modify the system trust store; these roots are local to this context instance. + /// + /// Accepts both `kCertificate` (parsed, not persisted) and `kCertSlot` handles. + /// The daemon validates each certificate immediately: + /// - Expired certificates cause the whole call to fail. + /// - Certificates already present in the configured system trust store are + /// accepted silently (idempotent union — no duplicate anchors). + /// + /// Replaces any previously set additional anchors on this context (set semantics). + /// + /// @param anchors Span of certificate handles (type = kCertificate or kCertSlot) + /// @return std::monostate on success, error if any handle is invalid or any certificate has expired + virtual score::Result SetAdditionalTrustAnchors( + score::cpp::span anchors) = 0; + + /// @brief Provides an OCSP response for revocation checking. + /// @param response_data DER-encoded OCSP response + /// @return std::monostate on success, error on parse failure + /// @note The context internally validates the OCSP responder's certificate + /// chain against the configured verification trust store. + virtual score::Result SetOcspResponse(score::cpp::span response_data) = 0; + + /// @brief References an imported CRL for revocation checking. + /// @param crl Handle to a previously imported CRL + /// @return std::monostate on success, error if handle is invalid + virtual score::Result SetCrl(const CryptoResourceId& crl) = 0; + + /// @brief Overrides the verification time. + /// @param epoch_seconds Verification time as seconds since Unix epoch + /// @return std::monostate on success + /// @note Default: current system time. Use this for testing or for + /// verifying certificates at a specific point in time. + virtual score::Result SetVerificationTime(int64_t epoch_seconds) = 0; + + /// @brief Sets the revocation checking strategy. + /// @param policy The revocation check policy to apply + /// @return std::monostate on success + /// @note Overrides the default policy set in the config. + virtual score::Result SetRevocationCheckPolicy(RevocationCheckPolicy policy) = 0; + + // ---- Execution ---- + + /// @brief Executes the configured certificate verification. + /// @return Verification result indicating validity or failure reason + /// @note At minimum, a certificate (or chain) and trust anchor must be set. + virtual score::Result Verify() = 0; + + protected: + ICertificateVerificationContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_CERTIFICATE_VERIFICATION_CONTEXT_HPP diff --git a/score/mw/crypto/api/future/contexts/i_cipher_context.hpp b/score/mw/crypto/api/future/contexts/i_cipher_context.hpp new file mode 100644 index 0000000..e1a7510 --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_cipher_context.hpp @@ -0,0 +1,118 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_CIPHER_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_CIPHER_CONTEXT_HPP + +#include "score/mw/crypto/api/contexts/i_streaming_output_context.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Unified interface for symmetric cipher operations (encryption and decryption). +/// +/// The direction (encrypt or decrypt) is selected via CipherContextConfig::SetDirection() +/// at context creation time. Supports streaming (Init → Update → Finalize) and +/// single-shot modes. The key and algorithm are bound at context creation via +/// CipherContextConfig. +/// +/// Exposes Init(), Reset(), Finalize(), and GetOutputSize() from base classes. +/// Init() uses the base optional-IV signature; for IV-based modes (AES-CBC, +/// AES-CTR, ChaCha20) an IV must be provided — passing nullopt returns +/// kUnsupportedOperation. For ECB mode (no IV), pass std::nullopt. +/// Update() uses the cipher-specific (input, output) signature. +/// +/// Compatible with classical ciphers (AES-CBC, AES-CTR, AES-ECB, ChaCha20) +/// and PQC key-encapsulation based hybrid encryption schemes. +/// +/// @par Example — encryption (streaming, CBC) +/// @code +/// CipherContextConfig cfg; +/// cfg.SetAlgorithm("AES-256-CBC") +/// .SetKey(key_slot) +/// .SetDirection(CipherDirection::kEncrypt); +/// auto cipher = ctx->CreateCipherContext(cfg).value(); +/// cipher->Init(iv); // span implicitly converts to optional +/// auto n = cipher->Update(plaintext, ciphertext_buf).value(); +/// auto m = cipher->Finalize(final_buf).value(); +/// @endcode +/// +/// @par Example — decryption (single-shot) +/// @code +/// CipherContextConfig cfg; +/// cfg.SetAlgorithm("AES-256-CBC") +/// .SetKey(key_slot) +/// .SetDirection(CipherDirection::kDecrypt); +/// auto cipher = ctx->CreateCipherContext(cfg).value(); +/// auto n = cipher->SingleShot(iv, ciphertext, plaintext_buf).value(); +/// @endcode +class ICipherContext : public IStreamingOutputContext +{ + public: + using Uptr = std::unique_ptr; + + ~ICipherContext() override = default; + + ICipherContext(const ICipherContext&) = delete; + ICipherContext& operator=(const ICipherContext&) = delete; + ICipherContext(ICipherContext&&) = default; + ICipherContext& operator=(ICipherContext&&) = default; + + // -- Streaming API (from base classes) -- + using IStreamingContext::Init; + using IStreamingContext::Reset; + using IStreamingOutputContext::Finalize; + using IStreamingOutputContext::GetOutputSize; + + /// @brief Processes a chunk of input data and writes output. + /// + /// When configured for encryption: input is plaintext, output is ciphertext. + /// When configured for decryption: input is ciphertext, output is plaintext. + /// + /// @param input Input data to encrypt or decrypt + /// @param output Output buffer for the result + /// @return Number of output bytes written on success, error on failure + /// @note Can be called multiple times after Init() for streaming operation. + virtual score::Result Update(score::cpp::span input, + score::cpp::span output) = 0; + + /// @brief Processes data in a single call (Init + Update* + Finalize combined). + /// + /// When configured for encryption: input is plaintext, output is ciphertext. + /// When configured for decryption: input is ciphertext, output is plaintext. + /// + /// @param iv Initialization vector / nonce + /// @param input Input data to encrypt or decrypt + /// @param output Output buffer for the result + /// @return Number of output bytes written on success, error on failure + virtual score::Result SingleShot(score::cpp::span iv, + score::cpp::span input, + score::cpp::span output) = 0; + + protected: + ICipherContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_CIPHER_CONTEXT_HPP diff --git a/score/mw/crypto/api/future/contexts/i_csr_generation_context.hpp b/score/mw/crypto/api/future/contexts/i_csr_generation_context.hpp new file mode 100644 index 0000000..2bce23e --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_csr_generation_context.hpp @@ -0,0 +1,104 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_CSR_GENERATION_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_CSR_GENERATION_CONTEXT_HPP + +#include "score/mw/crypto/api/certificate/i_csr_export.hpp" +#include "score/mw/crypto/api/common/types.hpp" +#include "score/mw/crypto/api/contexts/i_context.hpp" +#include "score/result/result.h" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Builder-style context for Certificate Signing Request (CSR) generation. +/// +/// Created via ICryptoContext::CreateCsrGenerationContext(). Configure the CSR +/// parameters via setter methods, then call Generate() to produce the CSR. +/// +/// Supports both classical algorithms (RSA-PSS, ECDSA) and PQC algorithms +/// (ML-DSA-65, SLH-DSA-SHA2-128s) for CSR signing. +/// +/// @par Example +/// @code +/// auto ctx = crypto_context->CreateCsrGenerationContext(config); +/// ctx.value()->SetSubjectKey(signing_key); +/// ctx.value()->SetSignatureAlgorithm("ML-DSA-65"); +/// ctx.value()->SetSubjectDn("CN=MyDevice,O=Corp,C=DE"); +/// ctx.value()->AddSubjectAltName("DNS:mydevice.local"); +/// auto csr = ctx.value()->Generate(); +/// @endcode +class ICsrGenerationContext : public IContext +{ + public: + using Uptr = std::unique_ptr; + + ~ICsrGenerationContext() override = default; + + ICsrGenerationContext(const ICsrGenerationContext&) = delete; + ICsrGenerationContext& operator=(const ICsrGenerationContext&) = delete; + ICsrGenerationContext(ICsrGenerationContext&&) = default; + ICsrGenerationContext& operator=(ICsrGenerationContext&&) = default; + + // ---- Configuration setters (call before Generate()) ---- + + /// @brief Sets the key for signing the CSR. + /// @param key Handle to the signing key (CryptoResourceId with type = kKey) + /// @return std::monostate on success, error if key resource is invalid + virtual score::Result SetSubjectKey(const CryptoResourceId& key) = 0; + + /// @brief Sets the CSR signature algorithm. + /// @param algorithm Algorithm identifier (e.g., "SHA-256-RSA", "ML-DSA-65") + /// @return std::monostate on success, error if algorithm is not supported + virtual score::Result SetSignatureAlgorithm(const AlgorithmId& algorithm) = 0; + + /// @brief Sets the subject distinguished name. + /// @param dn Subject DN string (e.g., "CN=MyDevice,O=Corp,C=DE") + /// @return std::monostate on success + virtual score::Result SetSubjectDn(std::string_view dn) = 0; + + /// @brief Adds a Subject Alternative Name (SAN). + /// @param san SAN entry (e.g., "DNS:mydevice.local", "IP:192.168.1.1") + /// @return std::monostate on success + /// @note Can be called multiple times to add multiple SANs. + virtual score::Result AddSubjectAltName(std::string_view san) = 0; + + /// @brief Sets the optional challenge password. + /// @param password Challenge password for the CSR + /// @return std::monostate on success + virtual score::Result SetChallengePassword(std::string_view password) = 0; + + // ---- Execution ---- + + /// @brief Generates the CSR from the configured parameters. + /// @return Export object providing access to the encoded CSR data + /// @note At minimum, subject key, signature algorithm, and subject DN + /// must be set before calling Generate(). + virtual score::Result Generate() = 0; + + protected: + ICsrGenerationContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_CSR_GENERATION_CONTEXT_HPP diff --git a/score/mw/crypto/api/future/contexts/i_random_context.hpp b/score/mw/crypto/api/future/contexts/i_random_context.hpp new file mode 100644 index 0000000..68f07ef --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_random_context.hpp @@ -0,0 +1,71 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_RANDOM_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_RANDOM_CONTEXT_HPP + +#include "score/mw/crypto/api/contexts/i_context.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Interface for random number generation. +/// +/// Non-streaming context — provides direct Generate() and Seed() operations. +/// The RNG source is bound at context creation via RandomContextConfig +/// (algorithm and optional provider). +/// +/// Suitable for generating IVs, nonces, key material, and other +/// cryptographic random data. For PQC key generation (e.g., ML-KEM), +/// the RNG is used internally by the key generation operation. +class IRandomContext : public IContext +{ + public: + using Uptr = std::unique_ptr; + + ~IRandomContext() override = default; + + IRandomContext(const IRandomContext&) = delete; + IRandomContext& operator=(const IRandomContext&) = delete; + IRandomContext(IRandomContext&&) = default; + IRandomContext& operator=(IRandomContext&&) = default; + + /// @brief Generates cryptographically secure random bytes. + /// @param output Buffer to fill with random data + /// @return Number of bytes generated on success, error on failure + virtual score::Result Generate(score::cpp::span output) = 0; + + /// @brief Seeds the random number generator with additional entropy. + /// @param seed Entropy data to mix into the RNG state + /// @return std::monostate on success, error on failure + /// @note Not all providers support explicit seeding. Some hardware RNGs + /// may ignore this call (returning success without action). + virtual score::Result Seed(score::cpp::span seed) = 0; + + protected: + IRandomContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_RANDOM_CONTEXT_HPP diff --git a/score/mw/crypto/api/future/contexts/i_sign_context.hpp b/score/mw/crypto/api/future/contexts/i_sign_context.hpp new file mode 100644 index 0000000..f8ea0fc --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_sign_context.hpp @@ -0,0 +1,84 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_SIGN_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_SIGN_CONTEXT_HPP + +#include "score/mw/crypto/api/contexts/i_streaming_output_context.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Interface for digital signature generation. +/// +/// Exposes Init(), Update(), and Reset() from base classes for streaming +/// message feeding. Adds SignFinalize() for signature production, +/// SingleShot() for convenience, and GetSignatureSize() for buffer sizing. +/// +/// Compatible with classical algorithms (ECDSA, RSA-PSS, EdDSA) and +/// PQC signature schemes (ML-DSA / Dilithium, SLH-DSA / SPHINCS+, +/// XMSS, LMS). PQC algorithms are specified via AlgorithmId string +/// (e.g., "ML-DSA-65", "SLH-DSA-SHA2-128s"). +class ISignContext : public IStreamingOutputContext +{ + public: + using Uptr = std::unique_ptr; + + ~ISignContext() override = default; + + ISignContext(const ISignContext&) = delete; + ISignContext& operator=(const ISignContext&) = delete; + ISignContext(ISignContext&&) = default; + ISignContext& operator=(ISignContext&&) = default; + + // -- Streaming API (from base classes) -- + using IStreamingContext::Init; + using IStreamingContext::Reset; + using IStreamingContext::Update; + + /// @brief Finalizes the signing operation and produces the signature. + /// @param signature Output buffer for the signature + /// @return Number of signature bytes written on success, error on failure + /// @note For PQC schemes like ML-DSA, signature sizes may be larger than + /// classical algorithms. Use GetSignatureSize() to pre-allocate. + virtual score::Result SignFinalize(score::cpp::span signature) = 0; + + /// @brief Signs data in a single call. + /// @param data Input data to sign + /// @param signature Output buffer for the signature + /// @return Number of signature bytes written on success, error on failure + virtual score::Result SingleShot(score::cpp::span data, + score::cpp::span signature) = 0; + + /// @brief Returns the expected signature size in bytes. + /// @note For PQC algorithms, this returns the fixed signature size specified + /// by the algorithm parameter set (e.g., 3293 bytes for ML-DSA-65). + virtual std::size_t GetSignatureSize() const noexcept = 0; + + protected: + ISignContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_SIGN_CONTEXT_HPP diff --git a/score/mw/crypto/api/future/contexts/i_verify_signature_context.hpp b/score/mw/crypto/api/future/contexts/i_verify_signature_context.hpp new file mode 100644 index 0000000..1f22b1e --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_verify_signature_context.hpp @@ -0,0 +1,77 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_CONTEXTS_I_VERIFY_SIGNATURE_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_CONTEXTS_I_VERIFY_SIGNATURE_CONTEXT_HPP + +#include "score/mw/crypto/api/contexts/i_streaming_context.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Interface for digital signature verification. +/// +/// Exposes Init(), Update(), and Reset() from base classes for streaming +/// message feeding. Adds VerifyFinalize() for signature checking and +/// SingleShot() for convenience. +/// +/// Compatible with classical (ECDSA, RSA-PSS, EdDSA) and PQC verification +/// algorithms (ML-DSA, SLH-DSA, XMSS, LMS). +/// +/// @note Does NOT inherit from IStreamingOutputContext since verification +/// produces a boolean result, not output bytes. +class IVerifySignatureContext : public IStreamingContext +{ + public: + using Uptr = std::unique_ptr; + + ~IVerifySignatureContext() override = default; + + IVerifySignatureContext(const IVerifySignatureContext&) = delete; + IVerifySignatureContext& operator=(const IVerifySignatureContext&) = delete; + IVerifySignatureContext(IVerifySignatureContext&&) = default; + IVerifySignatureContext& operator=(IVerifySignatureContext&&) = default; + + // -- Streaming API (from base classes) -- + using IStreamingContext::Init; + using IStreamingContext::Reset; + using IStreamingContext::Update; + + /// @brief Finalizes verification and checks the signature. + /// @param signature The signature to verify against the accumulated data + /// @return true if signature is valid, false if invalid, error on failure + virtual score::Result VerifyFinalize(score::cpp::span signature) = 0; + + /// @brief Verifies a signature in a single call. + /// @param data The signed data + /// @param signature The signature to verify + /// @return true if signature is valid, false if invalid, error on failure + virtual score::Result SingleShot(score::cpp::span data, + score::cpp::span signature) = 0; + + protected: + IVerifySignatureContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_CONTEXTS_I_VERIFY_SIGNATURE_CONTEXT_HPP diff --git a/score/mw/crypto/api/future/contexts/src/i_certificate_context_impl_guide.hpp b/score/mw/crypto/api/future/contexts/src/i_certificate_context_impl_guide.hpp new file mode 100644 index 0000000..dd49f92 --- /dev/null +++ b/score/mw/crypto/api/future/contexts/src/i_certificate_context_impl_guide.hpp @@ -0,0 +1,73 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_FUTURE_CONTEXTS_SRC_I_CERTIFICATE_MANAGEMENT_CONTEXT_IMPL_GUIDE_HPP +#define SCORE_MW_CRYPTO_API_FUTURE_CONTEXTS_SRC_I_CERTIFICATE_MANAGEMENT_CONTEXT_IMPL_GUIDE_HPP + +/// @file +/// @brief Implementation guidance for concrete ICertificateManagementContext subclasses. +/// +/// This is an **internal header** — not part of the public API. Include it +/// in the concrete implementation `.cpp` or internal `.hpp`, not in any +/// application-visible header. +/// +/// --- +/// +/// ## How extracted public key lifetime works +/// +/// `LoadCertificatePublicKey()` extracts a certificate's public key into an +/// ephemeral key resource (kKey, kEphemeral). The daemon ref-counts this key: +/// +/// | Event | Daemon action | +/// |--------------------------------------|----------------------------| +/// | LoadCertificatePublicKey() | Creates key, ref = 1 | +/// | CreateContext(config with key_id) | Validates key, ref++ | +/// | Guard destroyed (Release IPC) | ref--; free if ref == 0 | +/// | Context destroyed | ref-- for bound key | +/// | Client disconnect / crash | Bulk-free all client keys | +/// +/// --- +/// +/// ## How to produce guards in LoadCertificatePublicKey() +/// +/// Use `CryptoResourceGuardFactory::Make()` (defined in +/// `score/mw/crypto/api/common/src/crypto_resource_guard_factory.hpp`). +/// +/// @code +/// #include "score/mw/crypto/api/common/src/crypto_resource_guard_factory.hpp" +/// #include "score/mw/crypto/api/common/src/i_release_callback.hpp" +/// +/// class ConcreteCertContext : public score::mw::crypto::ICertificateManagementContext { +/// public: +/// score::Result> +/// LoadCertificatePublicKey(const score::mw::crypto::CryptoResourceId& cert) override +/// { +/// // 1. Send ExtractPublicKey IPC to daemon, receive assigned key ID +/// score::mw::crypto::CryptoResourceId key_id = /* IPC result */; +/// score::mw::crypto::AlgorithmId alg = /* IPC result */; +/// +/// // 2. ipc_release_cb_ is std::shared_ptr +/// auto guard = score::mw::crypto::CryptoResourceGuardFactory::Make( +/// ipc_release_cb_, key_id); +/// return std::make_pair(std::move(guard), alg); +/// } +/// +/// private: +/// std::shared_ptr ipc_release_cb_; +/// }; +/// @endcode + +#include "score/mw/crypto/api/common/src/crypto_resource_guard_factory.hpp" +#include "score/mw/crypto/api/common/src/i_release_callback.hpp" + +#endif // SCORE_MW_CRYPTO_API_FUTURE_CONTEXTS_SRC_I_CERTIFICATE_MANAGEMENT_CONTEXT_IMPL_GUIDE_HPP diff --git a/score/mw/crypto/api/future/objects/BUILD b/score/mw/crypto/api/future/objects/BUILD new file mode 100644 index 0000000..6a4e07d --- /dev/null +++ b/score/mw/crypto/api/future/objects/BUILD @@ -0,0 +1,33 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +# ============================================================================= +# Typed object interfaces — not yet active (IPC implementation pending). +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +# cc_library( +# name = "future_crypto_objects", +# hdrs = [ +# "i_cert_slot_object.hpp", +# "i_certificate_object.hpp", +# "i_data_object.hpp", +# "i_provider_object.hpp", +# "i_secure_object.hpp", +# ], +# deps = [ +# "//score/mw/crypto/api/common:crypto_common", +# "@score_baselibs//score/language/futurecpp", +# "@score_baselibs//score/result", +# ], +# ) diff --git a/score/mw/crypto/api/future/objects/i_cert_slot_object.hpp b/score/mw/crypto/api/future/objects/i_cert_slot_object.hpp new file mode 100644 index 0000000..637f7cb --- /dev/null +++ b/score/mw/crypto/api/future/objects/i_cert_slot_object.hpp @@ -0,0 +1,54 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_OBJECTS_I_CERT_SLOT_OBJECT_HPP +#define SCORE_MW_CRYPTO_API_OBJECTS_I_CERT_SLOT_OBJECT_HPP + +#include "score/mw/crypto/api/objects/i_crypto_object.hpp" + +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Typed view of a persistent certificate storage location. +/// +/// Provides occupancy query for a resource whose type is kCertSlot. +/// Obtained via ICryptoContext::GetCertSlotObject(). +class ICertSlotObject : public ICryptoObject +{ + public: + using Uptr = std::unique_ptr; + + ~ICertSlotObject() override = default; + + ICertSlotObject(const ICertSlotObject&) = delete; + ICertSlotObject& operator=(const ICertSlotObject&) = delete; + ICertSlotObject(ICertSlotObject&&) = default; + ICertSlotObject& operator=(ICertSlotObject&&) = default; + + /// @brief Whether the certificate slot currently holds a certificate. + virtual bool IsOccupied() const noexcept = 0; + + protected: + ICertSlotObject() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_OBJECTS_I_CERT_SLOT_OBJECT_HPP diff --git a/score/mw/crypto/api/future/objects/i_certificate_object.hpp b/score/mw/crypto/api/future/objects/i_certificate_object.hpp new file mode 100644 index 0000000..9e0c442 --- /dev/null +++ b/score/mw/crypto/api/future/objects/i_certificate_object.hpp @@ -0,0 +1,108 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_OBJECTS_I_CERTIFICATE_OBJECT_HPP +#define SCORE_MW_CRYPTO_API_OBJECTS_I_CERTIFICATE_OBJECT_HPP + +#include "score/mw/crypto/api/objects/i_crypto_object.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Typed view of a certificate resource. +/// +/// The single certificate abstraction used for both ephemeral (parsed from bytes) +/// and persistent (loaded from a slot) certificates. All instances are +/// daemon-backed and carry a valid `GetId()` from the moment they are obtained. +/// +/// **Lifecycle**: destroying this object releases the daemon-side resource. +/// For ephemeral certificates created by ParseCertificate(), the daemon frees +/// the resource when the last ICertificateObject referring to it is destroyed. +/// For persistent certificates loaded from a slot, the slot and its content +/// are unaffected — only the in-memory view object is released. +/// +/// **Persistence**: use ICertificateManagementContext::SaveCertificate() to +/// copy an ephemeral certificate to a persistent slot. The ephemeral copy +/// remains valid and is released independently when this object is destroyed. +/// +/// Provides field access, serial number, public key metadata, and public key +/// export. Certificates with PQC keys (ML-DSA, SLH-DSA, XMSS, LMS) may +/// produce larger export payloads — call GetPublicKeyExportSize() first. +class ICertificateObject : public ICryptoObject +{ + public: + using Uptr = std::unique_ptr; + + ~ICertificateObject() override = default; + + ICertificateObject(const ICertificateObject&) = delete; + ICertificateObject& operator=(const ICertificateObject&) = delete; + ICertificateObject(ICertificateObject&&) = default; + ICertificateObject& operator=(ICertificateObject&&) = default; + + /// @brief Returns the certificate subject distinguished name. + virtual std::string_view GetSubject() const noexcept = 0; + + /// @brief Returns the certificate issuer distinguished name. + virtual std::string_view GetIssuer() const noexcept = 0; + + /// @brief Returns the notBefore validity timestamp (seconds since Unix epoch). + virtual int64_t GetNotBefore() const noexcept = 0; + + /// @brief Returns the notAfter validity timestamp (seconds since Unix epoch). + virtual int64_t GetNotAfter() const noexcept = 0; + + /// @brief Returns the public key algorithm identifier. + /// @return Algorithm string (e.g., "RSA-2048", "ECDSA-P256", "ML-DSA-65") + virtual AlgorithmId GetPublicKeyAlgorithm() const noexcept = 0; + + /// @brief Returns the certificate serial number as a hex-encoded string. + virtual std::string GetSerialNumber() const = 0; + + /// @brief Returns the byte size of the DER-encoded SubjectPublicKeyInfo. + /// + /// Call this before ExportPublicKey() to determine the required buffer size. + /// PQC algorithms (e.g., ML-DSA-65) may have public keys in the kilobyte + /// range; always query rather than assuming a fixed upper bound. + /// + /// @return Required buffer size in bytes, or error on failure. + virtual score::Result GetPublicKeyExportSize() const noexcept = 0; + + /// @brief Exports the certificate's public key in DER (SubjectPublicKeyInfo) format. + /// + /// Call GetPublicKeyExportSize() first to allocate a correctly-sized buffer. + /// + /// @param output Buffer to receive the exported public key. Must be at least + /// GetPublicKeyExportSize() bytes. + /// @return Number of bytes written, or error if the buffer is too small. + virtual score::Result ExportPublicKey(score::cpp::span output) const = 0; + + protected: + ICertificateObject() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_OBJECTS_I_CERTIFICATE_OBJECT_HPP diff --git a/score/mw/crypto/api/future/objects/i_data_object.hpp b/score/mw/crypto/api/future/objects/i_data_object.hpp new file mode 100644 index 0000000..00baf84 --- /dev/null +++ b/score/mw/crypto/api/future/objects/i_data_object.hpp @@ -0,0 +1,63 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_OBJECTS_I_DATA_OBJECT_HPP +#define SCORE_MW_CRYPTO_API_OBJECTS_I_DATA_OBJECT_HPP + +#include "score/mw/crypto/api/objects/i_crypto_object.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Typed view of a generic data blob resource. +/// +/// Provides read access to arbitrary data stored within the +/// crypto daemon. Resource type is kDataObject. +class IDataObject : public ICryptoObject +{ + public: + using Uptr = std::unique_ptr; + + ~IDataObject() override = default; + + IDataObject(const IDataObject&) = delete; + IDataObject& operator=(const IDataObject&) = delete; + IDataObject(IDataObject&&) = default; + IDataObject& operator=(IDataObject&&) = default; + + /// @brief Reads the data object contents into the provided buffer. + /// @param output Destination buffer + /// @return Number of bytes written, or error if buffer is too small + virtual score::Result GetData(score::cpp::span output) const = 0; + + /// @brief Returns the size of the stored data in bytes. + virtual std::size_t GetSize() const noexcept = 0; + + protected: + IDataObject() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_OBJECTS_I_DATA_OBJECT_HPP diff --git a/score/mw/crypto/api/future/objects/i_provider_object.hpp b/score/mw/crypto/api/future/objects/i_provider_object.hpp new file mode 100644 index 0000000..768ffa9 --- /dev/null +++ b/score/mw/crypto/api/future/objects/i_provider_object.hpp @@ -0,0 +1,63 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_OBJECTS_I_PROVIDER_OBJECT_HPP +#define SCORE_MW_CRYPTO_API_OBJECTS_I_PROVIDER_OBJECT_HPP + +#include "score/mw/crypto/api/objects/i_crypto_object.hpp" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Typed view of a crypto provider resource. +/// +/// Provides provider type, name, and supported algorithm queries +/// for a resource whose type is kProvider. Obtained via +/// ICryptoContext::GetProviderObject(). +class IProviderObject : public ICryptoObject +{ + public: + using Uptr = std::unique_ptr; + + ~IProviderObject() override = default; + + IProviderObject(const IProviderObject&) = delete; + IProviderObject& operator=(const IProviderObject&) = delete; + IProviderObject(IProviderObject&&) = default; + IProviderObject& operator=(IProviderObject&&) = default; + + /// @brief Returns the provider type (kHardware, kSoftware, etc.). + virtual ProviderType GetProviderType() const noexcept = 0; + + /// @brief Returns the provider's human-readable name. + virtual std::string_view GetName() const noexcept = 0; + + /// @brief Returns the list of algorithms supported by this provider. + virtual std::vector GetSupportedAlgorithms() const = 0; + + protected: + IProviderObject() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_OBJECTS_I_PROVIDER_OBJECT_HPP diff --git a/score/mw/crypto/api/future/objects/i_secure_object.hpp b/score/mw/crypto/api/future/objects/i_secure_object.hpp new file mode 100644 index 0000000..a3728d7 --- /dev/null +++ b/score/mw/crypto/api/future/objects/i_secure_object.hpp @@ -0,0 +1,63 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_OBJECTS_I_SECURE_OBJECT_HPP +#define SCORE_MW_CRYPTO_API_OBJECTS_I_SECURE_OBJECT_HPP + +#include "score/mw/crypto/api/objects/i_crypto_object.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Typed view of a secure storage entry. +/// +/// Provides read access to opaque data stored in a secure element +/// or provider-managed area. Resource type is kSecureObject. +class ISecureObject : public ICryptoObject +{ + public: + using Uptr = std::unique_ptr; + + ~ISecureObject() override = default; + + ISecureObject(const ISecureObject&) = delete; + ISecureObject& operator=(const ISecureObject&) = delete; + ISecureObject(ISecureObject&&) = default; + ISecureObject& operator=(ISecureObject&&) = default; + + /// @brief Reads the secure object data into the provided buffer. + /// @param output Destination buffer + /// @return Number of bytes written, or error if buffer is too small + virtual score::Result GetData(score::cpp::span output) const = 0; + + /// @brief Returns the size of the stored data in bytes. + virtual std::size_t GetSize() const noexcept = 0; + + protected: + ISecureObject() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_OBJECTS_I_SECURE_OBJECT_HPP diff --git a/score/mw/crypto/api/i_crypto_context.hpp b/score/mw/crypto/api/i_crypto_context.hpp new file mode 100644 index 0000000..bdf176c --- /dev/null +++ b/score/mw/crypto/api/i_crypto_context.hpp @@ -0,0 +1,241 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_I_CRYPTO_CONTEXT_HPP +#define SCORE_MW_CRYPTO_API_I_CRYPTO_CONTEXT_HPP + +#include "score/mw/crypto/api/common/types.hpp" +#include "score/result/result.h" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +// ---- Forward declarations ---- +// Consumers include the specific headers they need; this header only +// requires forward declarations for parameter and return types. + +// Config types (used as const& parameters) +class HashContextConfig; +class KeyManagementContextConfig; +class MacContextConfig; + +// Operation contexts (returned as std::unique_ptr) +class IHashContext; +class IKeyManagementContext; +class IMacContext; + +// Typed object interfaces (returned as std::unique_ptr) +class IKeyObject; +class IKeySlotObject; + +// The following forward declarations are for contexts not yet active (IPC implementation pending). +// class AeadContextConfig; +// class CertificateContextConfig; +// class CertificateVerificationContextConfig; +// class CipherContextConfig; +// class CsrGenerationContextConfig; +// class RandomContextConfig; +// class SignContextConfig; +// class VerifySignatureContextConfig; +// class IAeadContext; +// class ICertificateManagementContext; +// class ICertificateVerificationContext; +// class ICipherContext; +// class ICsrGenerationContext; +// class IRandomContext; +// class ISignContext; +// class IVerifySignatureContext; +// class ICertificateObject; +// class ICertSlotObject; +// class IProviderObject; + +// TODO: Consider splitting this interface into multiple smaller interfaces (e.g. IContextFactory, ICapabilityQuerier). +/// @brief Factory, resource resolution, and typed object access for the crypto stack. +/// +/// ICryptoContext serves three purposes: +/// 1. **Resource resolution**: Convert app-defined string ResourceId to +/// daemon-assigned CryptoResourceId handles via ResolveResource(). +/// 2. **Context factory**: Create operation-specific contexts configured +/// with the resolved resource handles. +/// 3. **Typed object access**: Obtain specialized interfaces for +/// resource-type-specific queries (e.g., IKeyObject, IKeySlotObject). +/// +/// CryptoResourceId is the sole runtime handle for all resources. +/// Each context resolves incoming CryptoResourceId according to its own +/// requirements within its scope. +/// +/// Typical usage: +/// @code +/// auto slot = ctx->ResolveResource("MyMacKey", ResourceType::kKeySlot); +/// MacContextConfig config; +/// config.SetAlgorithm("HMAC-SHA-256"); +/// config.SetKeySlot(slot.value()); +/// auto mac_ctx = ctx->CreateMacContext(config); +/// // Context internally loads key material from the key slot; releases on destruction. +/// @endcode +/// +/// Typical usage (key management): +/// @code +/// auto slot = ctx->ResolveResource("MyAesKey", ResourceType::kKeySlot); +/// KeyManagementContextConfig cfg; +/// auto key_mgmt = ctx->CreateKeyManagementContext(cfg).value(); +/// auto guard = key_mgmt->LoadKey(slot.value()); // CryptoResourceGuard +/// // guard releases transient key material when it goes out of scope. +/// @endcode +class ICryptoContext +{ + public: + using Uptr = std::unique_ptr; + + virtual ~ICryptoContext() = default; + + ICryptoContext(const ICryptoContext&) = delete; + ICryptoContext& operator=(const ICryptoContext&) = delete; + ICryptoContext(ICryptoContext&&) = default; + ICryptoContext& operator=(ICryptoContext&&) = default; + + // TODO: Consider moving to the crypto stack interface, as the CryptoResouceId is also used by memory allocator as + // well. + // ---- Resource Resolution ---- + + /// @brief Resolves an app-defined resource name to a daemon handle. + /// @param resource_id Application-defined resource identifier (from config) + /// @param type Expected resource type for validation + /// @return CryptoResourceId handle for use in configs and operations + /// @note Called once per resource; result should be cached by the application. + /// Access control (uid) is enforced during resolution. + virtual score::Result ResolveResource(const ResourceId& resource_id, ResourceType type) = 0; + + // ---- Context Factory ---- + + /// @brief Creates a hash context. + /// @param config Hash configuration (algorithm required) + virtual score::Result> CreateHashContext(const HashContextConfig& config) = 0; + + /// @brief Creates a MAC context. + /// @param config MAC configuration (algorithm + key_slot required) + virtual score::Result> CreateMacContext(const MacContextConfig& config) = 0; + + /// @brief Creates a key management context. + /// @param config Key management configuration (provider optional) + virtual score::Result> CreateKeyManagementContext( + const KeyManagementContextConfig& config) = 0; + + // The following factory methods are declared but not yet active. + // Each is implemented in score/mw/crypto/api/future/contexts/ + // and will be moved here together with its IPC implementation. + + // virtual score::Result> CreateCipherContext( + // const CipherContextConfig& config) = 0; + + // virtual score::Result> CreateSignContext( + // const SignContextConfig& config) = 0; + + // virtual score::Result> CreateVerifySignatureContext( + // const VerifySignatureContextConfig& config) = 0; + + // virtual score::Result> CreateAeadContext( + // const AeadContextConfig& config) = 0; + + // virtual score::Result> CreateRandomContext( + // const RandomContextConfig& config) = 0; + + // virtual score::Result> CreateCertificateManagementContext( + // const CertificateContextConfig& config) = 0; + + // virtual score::Result> CreateCertificateVerificationContext( + // const CertificateVerificationContextConfig& config) = 0; + + // virtual score::Result> CreateCsrGenerationContext( + // const CsrGenerationContextConfig& config) = 0; + + // ---- Queries ---- + + /// @brief Queries algorithm capabilities and support. + /// @param algorithm Algorithm identifier to query + /// @return Capabilities including whether the algorithm is supported and available modes + virtual score::Result QueryCapabilities(const AlgorithmId& algorithm) = 0; + + /// @brief Queries system-wide capabilities across all providers. + /// @return Aggregated capabilities including all registered providers and + /// the union of their supported algorithms + virtual score::Result QueryCapabilities() = 0; + + // FUTURE: Uncomment when there is potentiant use for this in the future. + /// @brief Queries cross-provider compatibility for a resource. + /// @param resource Handle to the resource to query + /// @return Compatibility info with primary and secondary providers + /// @note Secondary providers can use this resource (e.g., SW-exported + /// key re-importable into another SW provider). The daemon computes + /// this based on algorithm support, key exportability, and provider + /// capabilities. + // virtual score::Result QueryProviderCompatibility(const CryptoResourceId& resource) = + // 0; + + /// @brief Gets provider information for the primary provider in CryptoResourceId. + /// @param resourceId CryptoResourceId + /// @return Provider info with type and name + virtual score::Result GetProviderInfo(const CryptoResourceId& resourceId) = 0; + + /// @brief Maps a numeric provider ID to human-readable metadata. + /// @param provider_id Numeric provider ID (from CryptoResourceId::primary_provider + /// or KeySlotInfo::compatible_providers) + /// @return Provider info with type and name + virtual score::Result GetProviderInfo(uint16_t provider_id) = 0; + + // ---- Typed Object Access ---- + // + // Obtain a specialised, read-only view of a resource identified by its + // CryptoResourceId. The resource type encoded in the id must match the + // requested interface (e.g., kKey for GetKeyObject). + + /// @brief Obtains a typed key object for the given resource. + /// @param id CryptoResourceId whose type must be kKey + /// @return IKeyObject for algorithm / persistence / exportability queries + virtual score::Result> GetKeyObject(const CryptoResourceId& id) = 0; + + /// @brief Obtains a typed key-slot object for the given resource. + /// @param id CryptoResourceId whose type must be kKeySlot + /// @return IKeySlotObject for slot state / allowed-algorithm queries + virtual score::Result> GetKeySlotObject(const CryptoResourceId& id) = 0; + + // FUTURE: Uncomment when the corresponding object interface is promoted + // from score/mw/crypto/api/future/objects/. + // ICertificateObject is the unified certificate view — used for both + // ephemeral (ParseCertificate result) and persistent (loaded from slot) + // certificates. Always has a valid GetId(). + + // virtual score::Result> GetCertificateObject( + // const CryptoResourceId& id) = 0; + + // virtual score::Result> GetCertSlotObject( + // const CryptoResourceId& id) = 0; + + // virtual score::Result> GetProviderObject( + // const CryptoResourceId& id) = 0; + + protected: + ICryptoContext() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_I_CRYPTO_CONTEXT_HPP diff --git a/score/mw/crypto/api/i_crypto_stack.hpp b/score/mw/crypto/api/i_crypto_stack.hpp new file mode 100644 index 0000000..4dd567b --- /dev/null +++ b/score/mw/crypto/api/i_crypto_stack.hpp @@ -0,0 +1,76 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_I_CRYPTO_STACK_HPP +#define SCORE_MW_CRYPTO_API_I_CRYPTO_STACK_HPP + +#include "score/mw/crypto/api/common/i_memory_allocator.hpp" +#include "score/mw/crypto/api/i_crypto_context.hpp" +#include "score/result/result.h" + +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Application-level entry point for cryptographic operations. +/// +/// The underlying daemon connection is managed internally and shared +/// across all ICryptoStack instances within the same process. All objects +/// obtained via this interface — contexts, allocators, and resource guards +/// — have independent lifetimes and may safely outlive this instance. +class ICryptoStack +{ + public: + using Uptr = std::unique_ptr; + + virtual ~ICryptoStack() = default; + + ICryptoStack(const ICryptoStack&) = delete; + ICryptoStack& operator=(const ICryptoStack&) = delete; + ICryptoStack(ICryptoStack&&) = default; + ICryptoStack& operator=(ICryptoStack&&) = default; + + /// @brief Creates a new crypto context for resource resolution and operations. + /// @return Unique pointer to the created context + /// @note Multiple contexts can be created from one stack for parallel + /// independent operation streams. + virtual score::Result CreateCryptoContext() = 0; + + /// @brief Returns the data-plane memory allocator. + /// @return Result containing ownership of the memory allocator. + /// On success, extract via `.value()`. On error, check `.error()`. + /// @note Allocated memory regions remain valid until explicitly destroyed + /// or the allocator is destroyed. + virtual score::Result GetMemoryAllocator() = 0; + + // FUTURE: Uncomment + /// @brief Returns the secure storage manager. + /// @return Result containing ownership of the secure storage manager. + /// On success, extract via `.value()`. On error, check `.error()`. + /// @note Provides application-level AEAD-encrypted blob storage backed by + /// the crypto daemon's key hierarchy. + // virtual score::Result GetSecureStorageManager() = 0; + + protected: + ICryptoStack() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_I_CRYPTO_STACK_HPP diff --git a/score/mw/crypto/api/objects/BUILD b/score/mw/crypto/api/objects/BUILD new file mode 100644 index 0000000..9570931 --- /dev/null +++ b/score/mw/crypto/api/objects/BUILD @@ -0,0 +1,32 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +#Copyright(c) 2026 by ETAS GmbH.All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "crypto_objects", + hdrs = [ + "i_crypto_object.hpp", + "i_key_object.hpp", + "i_key_slot_object.hpp", + "i_private_key_object.hpp", + "i_public_key_object.hpp", + "i_symmetric_key_object.hpp", + ], + includes = ["."], + visibility = ["//visibility:public"], + deps = [ + "//score/mw/crypto/api/common:crypto_common", + "@score_baselibs//score/language/futurecpp", + "@score_baselibs//score/result", + ], +) diff --git a/score/mw/crypto/api/objects/i_crypto_object.hpp b/score/mw/crypto/api/objects/i_crypto_object.hpp new file mode 100644 index 0000000..76e725e --- /dev/null +++ b/score/mw/crypto/api/objects/i_crypto_object.hpp @@ -0,0 +1,61 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_OBJECTS_I_CRYPTO_OBJECT_HPP +#define SCORE_MW_CRYPTO_API_OBJECTS_I_CRYPTO_OBJECT_HPP + +#include "score/mw/crypto/api/common/types.hpp" + +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Root base for all typed crypto objects. +/// +/// Provides the common identity and type accessors shared by every +/// specialised object interface. Objects are lightweight proxies +/// into daemon state, not owned data copies. +/// +/// Obtain typed objects via ICryptoContext accessor methods +/// (e.g., GetKeyObject(), GetKeySlotObject()). +class ICryptoObject +{ + public: + using Uptr = std::unique_ptr; + + virtual ~ICryptoObject() = default; + + ICryptoObject(const ICryptoObject&) = delete; + ICryptoObject& operator=(const ICryptoObject&) = delete; + ICryptoObject(ICryptoObject&&) = default; + ICryptoObject& operator=(ICryptoObject&&) = default; + + /// @brief Returns the underlying CryptoResourceId handle. + virtual CryptoResourceId GetId() const noexcept = 0; + + /// @brief Returns the resource type (kKey, kKeySlot, kCertificate, etc.). + virtual ResourceType GetType() const noexcept = 0; + + protected: + ICryptoObject() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_OBJECTS_I_CRYPTO_OBJECT_HPP diff --git a/score/mw/crypto/api/objects/i_key_object.hpp b/score/mw/crypto/api/objects/i_key_object.hpp new file mode 100644 index 0000000..1e5bf43 --- /dev/null +++ b/score/mw/crypto/api/objects/i_key_object.hpp @@ -0,0 +1,78 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_OBJECTS_I_KEY_OBJECT_HPP +#define SCORE_MW_CRYPTO_API_OBJECTS_I_KEY_OBJECT_HPP + +#include "score/mw/crypto/api/objects/i_crypto_object.hpp" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Typed view of key material (symmetric or asymmetric). +/// +/// Provides algorithm, persistence, exportability, and length queries +/// for a resource whose type is kKey. Obtained via +/// ICryptoContext::GetKeyObject(). +/// +/// Sub-types ISymmetricKeyObject, IPublicKeyObject, and +/// IPrivateKeyObject add role-specific queries. +class IKeyObject : public ICryptoObject +{ + public: + using Uptr = std::unique_ptr; + + ~IKeyObject() override = default; + + IKeyObject(const IKeyObject&) = delete; + IKeyObject& operator=(const IKeyObject&) = delete; + IKeyObject(IKeyObject&&) = default; + IKeyObject& operator=(IKeyObject&&) = default; + + /// @brief Returns the algorithm bound to this key. + virtual AlgorithmId GetAlgorithm() const noexcept = 0; + + /// @brief Returns the persistence model (kPersistent or kEphemeral). + virtual ResourcePersistence GetPersistence() const noexcept = 0; + + /// @brief Whether the key material can be exported / unwrapped. + virtual bool IsExportable() const noexcept = 0; + + /// @brief Returns the key length in bits. + virtual std::size_t GetKeyLength() const noexcept = 0; + + /// @brief Returns the operations this key is permitted to perform. + /// + /// For keys loaded from a slot, this reflects the slot's provisioned + /// permissions. For ephemeral keys generated with explicit permissions, + /// this reflects the requested permission set. For ephemeral keys + /// generated without explicit permissions, returns kAll. + /// + /// @return Bitmask of KeyOperationPermission values. + virtual KeyOperationPermission GetPermittedOperations() const noexcept = 0; + + protected: + IKeyObject() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_OBJECTS_I_KEY_OBJECT_HPP diff --git a/score/mw/crypto/api/objects/i_key_slot_object.hpp b/score/mw/crypto/api/objects/i_key_slot_object.hpp new file mode 100644 index 0000000..fe193d1 --- /dev/null +++ b/score/mw/crypto/api/objects/i_key_slot_object.hpp @@ -0,0 +1,81 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_OBJECTS_I_KEY_SLOT_OBJECT_HPP +#define SCORE_MW_CRYPTO_API_OBJECTS_I_KEY_SLOT_OBJECT_HPP + +#include "score/mw/crypto/api/objects/i_crypto_object.hpp" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Typed view of a persistent key storage location. +/// +/// Provides state, algorithm constraint, and provider binding queries +/// for a resource whose type is kKeySlot. Key slots represent only +/// logical persistent storage; ephemeral keys have no slot. +/// +/// Obtained via ICryptoContext::GetKeySlotObject(). +class IKeySlotObject : public ICryptoObject +{ + public: + using Uptr = std::unique_ptr; + + ~IKeySlotObject() override = default; + + IKeySlotObject(const IKeySlotObject&) = delete; + IKeySlotObject& operator=(const IKeySlotObject&) = delete; + IKeySlotObject(IKeySlotObject&&) = default; + IKeySlotObject& operator=(IKeySlotObject&&) = default; + + /// @brief Returns full slot metadata. + virtual KeySlotInfo GetInfo() const = 0; + + /// @brief Returns the current slot state (kEmpty, kOccupied, kLocked). + virtual KeySlotState GetState() const noexcept = 0; + + /// @brief Returns the algorithm constraint for this slot. + virtual AlgorithmId GetAllowedAlgorithm() const noexcept = 0; + + /// @brief Returns the owning provider's numeric ID. + virtual uint16_t GetPrimaryProvider() const noexcept = 0; + + /// @brief Returns providers that can also use keys in this slot. + virtual std::vector GetCompatibleProviders() const = 0; + + /// @brief Returns the permitted operations for keys in this slot. + /// + /// The daemon enforces these permissions at context creation time. + /// If a key from this slot is used in a context that requires an + /// operation not included in the returned permission set, the + /// daemon returns CryptoErrorCode::kKeyOperationNotPermitted. + /// + /// @return Bitmask of KeyOperationPermission values. + virtual KeyOperationPermission GetPermittedOperations() const noexcept = 0; + + protected: + IKeySlotObject() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_OBJECTS_I_KEY_SLOT_OBJECT_HPP diff --git a/score/mw/crypto/api/objects/i_private_key_object.hpp b/score/mw/crypto/api/objects/i_private_key_object.hpp new file mode 100644 index 0000000..104a21b --- /dev/null +++ b/score/mw/crypto/api/objects/i_private_key_object.hpp @@ -0,0 +1,71 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_OBJECTS_I_PRIVATE_KEY_OBJECT_HPP +#define SCORE_MW_CRYPTO_API_OBJECTS_I_PRIVATE_KEY_OBJECT_HPP + +#include "score/mw/crypto/api/common/crypto_resource_guard.hpp" +#include "score/mw/crypto/api/objects/i_key_object.hpp" +#include "score/result/result.h" + +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Typed view of private key material (asymmetric only). +/// +/// Extends IKeyObject with the ability to derive the corresponding public key. +/// Obtained by down-casting an IKeyObject whose role is "private", or returned +/// directly from GenerateKey() when the algorithm is asymmetric (RSA, ECDH, ML-DSA, etc.). +/// +/// The public key is logically derived from the private key (zero-copy in the daemon); +/// no separate generation is performed. Implementations construct the returned guard +/// using `CryptoResourceGuardFactory::Make()` with the daemon-provided public key ID. +class IPrivateKeyObject : public IKeyObject +{ + public: + using Uptr = std::unique_ptr; + + ~IPrivateKeyObject() override = default; + + IPrivateKeyObject(const IPrivateKeyObject&) = delete; + IPrivateKeyObject& operator=(const IPrivateKeyObject&) = delete; + IPrivateKeyObject(IPrivateKeyObject&&) = default; + IPrivateKeyObject& operator=(IPrivateKeyObject&&) = default; + + /// @brief Derives an ephemeral public key from this private key. + /// + /// The returned guard owns an ephemeral copy of the public key in the daemon. + /// The public key is automatically cleaned up when the guard is destroyed (RAII). + /// This is a lightweight operation; no key material is recomputed. + /// + /// @return CryptoResourceGuard wrapping the ephemeral public key. + /// Can be used in any API accepting CryptoResourceId via implicit conversion. + /// @note The guard provides access to IPublicKeyObject via typed downcasting. + /// Implementations construct the returned guard via CryptoResourceGuardFactory::Make() + /// using the IPC layer's release callback. + virtual score::Result GetPublicKey() const = 0; + + protected: + IPrivateKeyObject() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_OBJECTS_I_PRIVATE_KEY_OBJECT_HPP diff --git a/score/mw/crypto/api/objects/i_public_key_object.hpp b/score/mw/crypto/api/objects/i_public_key_object.hpp new file mode 100644 index 0000000..5efa0d4 --- /dev/null +++ b/score/mw/crypto/api/objects/i_public_key_object.hpp @@ -0,0 +1,60 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_OBJECTS_I_PUBLIC_KEY_OBJECT_HPP +#define SCORE_MW_CRYPTO_API_OBJECTS_I_PUBLIC_KEY_OBJECT_HPP + +#include "score/mw/crypto/api/objects/i_key_object.hpp" +#include "score/result/result.h" +#include "score/span.hpp" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Typed view of public key material. +/// +/// Extends IKeyObject with the ability to export the public key bytes. +/// Obtained by down-casting an IKeyObject whose role is "public". +class IPublicKeyObject : public IKeyObject +{ + public: + using Uptr = std::unique_ptr; + + ~IPublicKeyObject() override = default; + + IPublicKeyObject(const IPublicKeyObject&) = delete; + IPublicKeyObject& operator=(const IPublicKeyObject&) = delete; + IPublicKeyObject(IPublicKeyObject&&) = default; + IPublicKeyObject& operator=(IPublicKeyObject&&) = default; + + /// @brief Exports the public key bytes into the provided buffer. + /// @param output Destination buffer + /// @return Number of bytes written, or error if buffer is too small + virtual score::Result ExportPublicKey(score::cpp::span output) const = 0; + + protected: + IPublicKeyObject() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_OBJECTS_I_PUBLIC_KEY_OBJECT_HPP diff --git a/score/mw/crypto/api/objects/i_symmetric_key_object.hpp b/score/mw/crypto/api/objects/i_symmetric_key_object.hpp new file mode 100644 index 0000000..413f9fb --- /dev/null +++ b/score/mw/crypto/api/objects/i_symmetric_key_object.hpp @@ -0,0 +1,57 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_OBJECTS_I_SYMMETRIC_KEY_OBJECT_HPP +#define SCORE_MW_CRYPTO_API_OBJECTS_I_SYMMETRIC_KEY_OBJECT_HPP + +#include "score/mw/crypto/api/objects/i_key_object.hpp" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Typed view of symmetric key material. +/// +/// Extends IKeyObject with symmetric-specific queries such as +/// allowed cipher modes. Obtained by down-casting an IKeyObject +/// whose algorithm is symmetric. +class ISymmetricKeyObject : public IKeyObject +{ + public: + using Uptr = std::unique_ptr; + + ~ISymmetricKeyObject() override = default; + + ISymmetricKeyObject(const ISymmetricKeyObject&) = delete; + ISymmetricKeyObject& operator=(const ISymmetricKeyObject&) = delete; + ISymmetricKeyObject(ISymmetricKeyObject&&) = default; + ISymmetricKeyObject& operator=(ISymmetricKeyObject&&) = default; + + /// @brief Returns the cipher modes allowed for this key (e.g., "CBC", "GCM"). + virtual std::vector GetAllowedModes() const = 0; + + protected: + ISymmetricKeyObject() = default; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_OBJECTS_I_SYMMETRIC_KEY_OBJECT_HPP diff --git a/score/mw/crypto/api/src/crypto_context_impl.cpp b/score/mw/crypto/api/src/crypto_context_impl.cpp new file mode 100644 index 0000000..f12094b --- /dev/null +++ b/score/mw/crypto/api/src/crypto_context_impl.cpp @@ -0,0 +1,403 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/mw/crypto/api/src/crypto_context_impl.hpp" + +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/mw/crypto/api/common/error_domain.hpp" +#include "score/mw/crypto/api/common/types.hpp" +#include "score/mw/crypto/api/config/hash_context_config.hpp" +#include "score/mw/crypto/api/config/key_management_context_config.hpp" +#include "score/mw/crypto/api/config/mac_context_config.hpp" +#include "score/mw/crypto/api/contexts/src/hash_context_impl.hpp" +#include "score/mw/crypto/api/contexts/src/key_management_context_impl.hpp" +#include "score/mw/crypto/api/contexts/src/mac_context_impl.hpp" +#include "score/mw/crypto/api/src/provider_type_converter.hpp" + +#include "score/crypto/api/control_plane/i_connection.hpp" +#include "score/result/result.h" + +#include +#include +#include +#include + +// Full definitions needed for Result> return types +#include "score/mw/crypto/api/contexts/i_hash_context.hpp" +#include "score/mw/crypto/api/contexts/i_key_management_context.hpp" +#include "score/mw/crypto/api/contexts/i_mac_context.hpp" +#include "score/mw/crypto/api/objects/i_key_object.hpp" +#include "score/mw/crypto/api/objects/i_key_slot_object.hpp" + +#include "score/crypto/daemon/mediator/mediator_operations.hpp" + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +CryptoContextImpl::CryptoContextImpl(std::shared_ptr connection) + : m_connection(std::move(connection)) +{ +} + +CryptoContextImpl::~CryptoContextImpl() {} + +// --------------------------------------------------------------------------- +// Context Factory — Hash +// --------------------------------------------------------------------------- + +score::Result> CryptoContextImpl::CreateHashContext(const HashContextConfig& config) +{ + namespace proto = ::score::crypto::daemon::control_plane::protocol; + + // Send CTX_CREATE to the daemon to create a server-side hash context. + // The daemon will validate the algorithm and return the context_id and digest_size. + auto request_builder = proto::ControlRequestBuilder() + .forDataNodeId(m_connection->GetConnectionNodeId()) + .operation(score::crypto::daemon::mediator::operations::CreateContext()) + .with_in_string("HASH") + .with_in_string(config.algorithm); + + if (config.provider_type.has_value()) + { + request_builder = + request_builder.with_in_val_uint8(ProviderTypeConverter::ToWireValue(config.provider_type.value())); + } + else + { + request_builder = request_builder.with_no_param(); + } + + auto control_req_result = request_builder.build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: Failed to build CTX_CREATE request\n"; + return score::Result>{ + score::unexpect, MakeError(CryptoErrorCode::kContextCreationFailed, "Failed to build CTX_CREATE request")}; + } + + // Send CTX_CREATE request to daemon + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + // Validate CTX_CREATE response + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation(score::crypto::daemon::mediator::operations::CreateContext()).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result>{ + score::unexpect, MakeError(CryptoErrorCode::kContextCreationFailed, "CTX_CREATE daemon response invalid")}; + } + + auto ctx_id_result = validator.getParameterAt(0, 0); + if (!ctx_id_result.has_value()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: CTX_CREATE response has invalid context_id type\n"; + return score::Result>{ + score::unexpect, + MakeError(CryptoErrorCode::kContextCreationFailed, "CTX_CREATE response has invalid context_id type")}; + } + + const uint64_t context_id = ctx_id_result.value(); + auto hash_ctx = std::make_unique(m_connection, context_id, config.algorithm); + + return hash_ctx; +} + +// --------------------------------------------------------------------------- +// Resource Resolution +// --------------------------------------------------------------------------- + +score::Result CryptoContextImpl::ResolveResource(const ResourceId& resource_id, ResourceType type) +{ + namespace proto = ::score::crypto::daemon::control_plane::protocol; + + auto control_req_result = proto::ControlRequestBuilder() + .forDataNodeId(m_connection->GetConnectionNodeId()) + .operation(score::crypto::daemon::mediator::operations::ResolveResource()) + .with_in_string(resource_id) + .with_in_val_uint8(static_cast(type)) + .build(); + + if (!control_req_result.has_value()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: Failed to build RESOURCE_RESOLVE request\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kInternalError, "Failed to build RESOURCE_RESOLVE request")}; + } + + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation(score::crypto::daemon::mediator::operations::ResolveResource()).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kInternalError, "RESOURCE_RESOLVE daemon response invalid")}; + } + + auto id_result = validator.getParameterAt(0, 0); + if (!id_result.has_value()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: RESOURCE_RESOLVE response missing resource_id\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kInternalError, "RESOURCE_RESOLVE response missing resource_id")}; + } + + auto type_result = validator.getParameterAt(0, 1); + if (!type_result.has_value()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: RESOURCE_RESOLVE response missing type\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kInternalError, "RESOURCE_RESOLVE response missing type")}; + } + + auto persistence_result = validator.getParameterAt(0, 2); + if (!persistence_result.has_value()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: RESOURCE_RESOLVE response missing persistence\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kInternalError, "RESOURCE_RESOLVE response missing persistence")}; + } + + auto primary_provider = validator.getParameterAt(0, 3); + if (!primary_provider.has_value()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: RESOURCE_RESOLVE response missing primary_provider\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kInternalError, "RESOURCE_RESOLVE response missing primary_provider")}; + } + + CryptoResourceId resolved{}; + resolved.id = id_result.value(); + resolved.type = static_cast(type_result.value()); + resolved.persistence = + persistence_result.value() ? ResourcePersistence::kPersistent : ResourcePersistence::kEphemeral; + resolved.primary_provider = primary_provider.value(); + + return resolved; +} + +// --------------------------------------------------------------------------- +// Context Factory stubs — not yet implemented +// --------------------------------------------------------------------------- + +score::Result> CryptoContextImpl::CreateMacContext(const MacContextConfig& config) +{ + namespace proto = ::score::crypto::daemon::control_plane::protocol; + + if (config.key.id == 0) + { + std::cerr << "[API][CryptoContextImpl] ERROR: CreateMacContext invalid / missing key id\n"; + return score::Result>{ + score::unexpect, + MakeError(CryptoErrorCode::kContextCreationFailed, "CreateMacContext invalid / missing key id")}; + } + + if (config.key.type != ResourceType::kKey && config.key.type != ResourceType::kKeySlot) + { + std::cerr << "[API][CryptoContextImpl] ERROR: CreateMacContext invalid key type\n"; + return score::Result>{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "CreateMacContext invalid key type")}; + } + + // Send CTX_CREATE to the daemon to create a server-side MAC context. + // MAC context requires: context type "MAC", algorithm, and key id. + auto request_builder = proto::ControlRequestBuilder() + .forDataNodeId(m_connection->GetConnectionNodeId()) + .operation(score::crypto::daemon::mediator::operations::CreateContext()) + .with_in_string("MAC") + .with_in_string(config.algorithm); + + if (config.provider_type.has_value()) + { + request_builder = + request_builder.with_in_val_uint8(ProviderTypeConverter::ToWireValue(config.provider_type.value())); + } + else + { + request_builder = request_builder.with_no_param(); + } + + request_builder = request_builder.with_in_val_uint64(config.key.id); + + // Serialize operation_mode (param[4]) so the daemon can route to C_Sign* or C_Verify*. + request_builder = request_builder.with_in_val_uint8(static_cast(config.operation_mode)); + + auto control_req_result = request_builder.build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: Failed to build CTX_CREATE request for MAC\n"; + return score::Result>{ + score::unexpect, + MakeError(CryptoErrorCode::kContextCreationFailed, "Failed to build CTX_CREATE request for MAC")}; + } + + // Send CTX_CREATE request to daemon + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + // Validate CTX_CREATE response + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation(score::crypto::daemon::mediator::operations::CreateContext()).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result>{ + score::unexpect, + MakeError(CryptoErrorCode::kContextCreationFailed, "CTX_CREATE MAC daemon response invalid")}; + } + + auto ctx_id_result = validator.getParameterAt(0, 0); + if (!ctx_id_result.has_value()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: CTX_CREATE MAC response has invalid context_id type\n"; + return score::Result>{ + score::unexpect, + MakeError(CryptoErrorCode::kContextCreationFailed, "CTX_CREATE MAC response has invalid context_id type")}; + } + + const uint64_t context_id = ctx_id_result.value(); + auto mac_ctx = std::make_unique(m_connection, context_id, config.algorithm); + + return mac_ctx; +} + +score::Result> CryptoContextImpl::CreateKeyManagementContext( + const KeyManagementContextConfig& config) +{ + namespace proto = ::score::crypto::daemon::control_plane::protocol; + + // Send CTX_CREATE to the daemon to create a server-side key management context. + auto request_builder = proto::ControlRequestBuilder() + .forDataNodeId(m_connection->GetConnectionNodeId()) + .operation(score::crypto::daemon::mediator::operations::CreateContext()) + .with_in_string("KEY_MANAGEMENT") + .with_in_string(""); // no algorithm for key management + + if (config.provider_type.has_value()) + { + request_builder = + request_builder.with_in_val_uint8(ProviderTypeConverter::ToWireValue(config.provider_type.value())); + } + else + { + request_builder = request_builder.with_no_param(); + } + + auto control_req_result = request_builder.build(); + if (!control_req_result.has_value()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: Failed to build CTX_CREATE request for KEY_MGMT\n"; + return score::Result>{ + score::unexpect, + MakeError(CryptoErrorCode::kContextCreationFailed, "Failed to build CTX_CREATE request for KEY_MGMT")}; + } + + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation(score::crypto::daemon::mediator::operations::CreateContext()).expectSuccess(); + + if (!validator.isValid()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: " << validator.getError() << "\n"; + return score::Result>{ + score::unexpect, + MakeError(CryptoErrorCode::kContextCreationFailed, "CTX_CREATE KEY_MGMT daemon response invalid")}; + } + + auto ctx_id_result = validator.getParameterAt(0, 0); + if (!ctx_id_result.has_value()) + { + std::cerr << "[API][CryptoContextImpl] ERROR: CTX_CREATE KEY_MGMT response has invalid context_id type\n"; + return score::Result>{ + score::unexpect, + MakeError(CryptoErrorCode::kContextCreationFailed, + "CTX_CREATE KEY_MGMT response has invalid context_id type")}; + } + + const uint64_t context_id = ctx_id_result.value(); + auto key_mgmt_ctx = std::make_unique(m_connection, context_id); + + return key_mgmt_ctx; +} + +// --------------------------------------------------------------------------- +// Queries (TODO) +// --------------------------------------------------------------------------- + +score::Result CryptoContextImpl::QueryCapabilities(const AlgorithmId& /*algorithm*/) +{ + // TODO: Implement algorithm capability query via daemon IPC + std::cerr << "[API][CryptoContextImpl] ERROR: QueryCapabilities(algorithm) not yet implemented\n"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kUnsupportedOperation, "QueryCapabilities(algorithm) not yet implemented")}; +} + +score::Result CryptoContextImpl::QueryCapabilities() +{ + // TODO: Implement system capability query via daemon IPC + std::cerr << "[API][CryptoContextImpl] ERROR: QueryCapabilities() not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "QueryCapabilities() not yet implemented")}; +} + +score::Result CryptoContextImpl::GetProviderInfo(uint16_t /*provider_id*/) +{ + // TODO: Implement provider info query via daemon IPC + std::cerr << "[API][CryptoContextImpl] ERROR: GetProviderInfo not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "GetProviderInfo not yet implemented")}; +} + +score::Result CryptoContextImpl::GetProviderInfo(const CryptoResourceId& /*resourceId*/) +{ + // TODO: Implement provider info query via daemon IPC + std::cerr << "[API][CryptoContextImpl] ERROR: GetProviderInfo not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "GetProviderInfo not yet implemented")}; +} + +// --------------------------------------------------------------------------- +// Typed Object Access (TODO) +// --------------------------------------------------------------------------- + +score::Result> CryptoContextImpl::GetKeyObject(const CryptoResourceId& /*id*/) +{ + // TODO: Implement key object retrieval via daemon IPC + std::cerr << "[API][CryptoContextImpl] ERROR: GetKeyObject not yet implemented\n"; + return score::Result>{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "GetKeyObject not yet implemented")}; +} + +score::Result> CryptoContextImpl::GetKeySlotObject(const CryptoResourceId& /*id*/) +{ + // TODO: Implement key slot object retrieval via daemon IPC + std::cerr << "[API][CryptoContextImpl] ERROR: GetKeySlotObject not yet implemented\n"; + return score::Result>{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "GetKeySlotObject not yet implemented")}; +} + +} // namespace crypto +} // namespace mw +} // namespace score diff --git a/score/mw/crypto/api/src/crypto_context_impl.hpp b/score/mw/crypto/api/src/crypto_context_impl.hpp new file mode 100644 index 0000000..f6941d3 --- /dev/null +++ b/score/mw/crypto/api/src/crypto_context_impl.hpp @@ -0,0 +1,81 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_SRC_CRYPTO_CONTEXT_IMPL_HPP +#define SCORE_MW_CRYPTO_API_SRC_CRYPTO_CONTEXT_IMPL_HPP + +#include "score/mw/crypto/api/common/types.hpp" +#include "score/mw/crypto/api/i_crypto_context.hpp" + +#include "score/crypto/api/control_plane/i_connection.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" + +#include "score/result/result.h" + +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Concrete ICryptoContext implementation that delegates to the crypto daemon via IPC. +/// +/// Factory methods to create CryptoContext (CreateHashContext, etc.) +/// send context-creation requests to the daemon and wrap the returned context_id +/// in the corresponding concrete context implementation. +class CryptoContextImpl final : public ICryptoContext +{ + public: + /// @brief Constructs a crypto context with an established connection. + /// @param connection Shared ownership of the connection (which contains the DataNodeId) + CryptoContextImpl(std::shared_ptr connection); + + ~CryptoContextImpl() override; + + // Deleted special members to prevent copying and moving + CryptoContextImpl(const CryptoContextImpl&) = delete; + CryptoContextImpl& operator=(const CryptoContextImpl&) = delete; + CryptoContextImpl(CryptoContextImpl&&) = delete; + CryptoContextImpl& operator=(CryptoContextImpl&&) = delete; + + // -- Resource Resolution -- + score::Result ResolveResource(const ResourceId& resource_id, ResourceType type) override; + + // -- Context Factory -- + score::Result> CreateHashContext(const HashContextConfig& config) override; + score::Result> CreateMacContext(const MacContextConfig& config) override; + score::Result> CreateKeyManagementContext( + const KeyManagementContextConfig& config) override; + + // -- Queries -- + score::Result QueryCapabilities(const AlgorithmId& algorithm) override; + score::Result QueryCapabilities() override; + score::Result GetProviderInfo(uint16_t provider_id) override; + score::Result GetProviderInfo(const CryptoResourceId& resourceId) override; + + // -- Typed Object Access -- + score::Result> GetKeyObject(const CryptoResourceId& id) override; + score::Result> GetKeySlotObject(const CryptoResourceId& id) override; + + private: + std::shared_ptr m_connection; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_SRC_CRYPTO_CONTEXT_IMPL_HPP diff --git a/score/mw/crypto/api/src/crypto_stack_factory.cpp b/score/mw/crypto/api/src/crypto_stack_factory.cpp new file mode 100644 index 0000000..2860677 --- /dev/null +++ b/score/mw/crypto/api/src/crypto_stack_factory.cpp @@ -0,0 +1,106 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/mw/crypto/api/crypto_stack_factory.hpp" + +#include "score/mw/crypto/api/common/error_domain.hpp" +#include "score/mw/crypto/api/i_crypto_stack.hpp" +#include "score/mw/crypto/api/src/crypto_stack_impl.hpp" + +#include "score/crypto/api/control_plane/connection_factory.hpp" +#include "score/crypto/api/control_plane/i_connection.hpp" +#include "score/crypto/daemon/control_plane/control_operations.h" +#include "score/crypto/daemon/control_plane/control_protocol.h" + +#include "score/result/result.h" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +score::Result CreateCryptoStack(const CryptoStackConfig& config) +{ + if (config.connection_endpoint.empty()) + { + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kInvalidArgument, "Connection endpoint must not be empty")}; + } + + // Create socket connection to daemon + score::crypto::api::control_plane::ConnectionFactory factory; + auto connection_result = factory.CreateConnection(config.connection_endpoint); + if (!connection_result.has_value()) + { + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kConnectionFailed, "Failed to create socket connection")}; + } + + auto connection = + std::shared_ptr(std::move(connection_result.value())); + + // Send CONNECTION_OPEN to daemon to establish control plane connection + namespace proto = ::score::crypto::daemon::control_plane::protocol; + namespace ctrl = ::score::crypto::daemon::control_plane; + + auto request_res = proto::ControlRequestBuilder() + .forDataNodeId(0) // No connection ID yet during initial open + .operation(ctrl::operations::OpenConnection()) + .build(); + + if (!request_res.has_value()) + { + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kConnectionFailed, "Failed to build CONNECTION_OPEN request")}; + } + + auto response_res = connection->SendRequest(request_res.value()); + + // Validate response and extract DataNodeId + auto validator = proto::ControlResponseValidator::FromResult(response_res); + validator.expectOperation(ctrl::operations::OpenConnection()).expectSuccess(); + + if (!validator.isValid()) + { + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kConnectionFailed, + "Failed to validate CONNECTION_OPEN response: " + validator.getError())}; + } + + // Extract connection DataNodeId from response parameter at index 0 + auto connection_node_id_result = validator.getParameterAt(0, 0); + if (!connection_node_id_result.has_value()) + { + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kConnectionFailed, + "Failed to extract connection DataNodeId from CONNECTION_OPEN response")}; + } + + const uint64_t connection_node_id = connection_node_id_result.value(); + + // Set the connection node ID on the connection itself for lifecycle management + connection->SetConnectionNodeId(connection_node_id); + + return std::make_unique(config, connection); +} + +} // namespace crypto +} // namespace mw +} // namespace score diff --git a/score/mw/crypto/api/src/crypto_stack_impl.cpp b/score/mw/crypto/api/src/crypto_stack_impl.cpp new file mode 100644 index 0000000..52082bf --- /dev/null +++ b/score/mw/crypto/api/src/crypto_stack_impl.cpp @@ -0,0 +1,76 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/mw/crypto/api/src/crypto_stack_impl.hpp" + +#include "score/mw/crypto/api/common/error_domain.hpp" +#include "score/mw/crypto/api/common/i_memory_allocator.hpp" +#include "score/mw/crypto/api/crypto_stack_factory.hpp" +#include "score/mw/crypto/api/i_crypto_context.hpp" +#include "score/mw/crypto/api/src/crypto_context_impl.hpp" + +#include "score/crypto/api/control_plane/i_connection.hpp" + +#include "score/result/result.h" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +CryptoStackImpl::CryptoStackImpl(const CryptoStackConfig& config, + std::shared_ptr connection) + : m_config(config), m_connection(std::move(connection)) +{ +} + +CryptoStackImpl::~CryptoStackImpl() +{ + // No cleanup needed - connection is managed by shared_ptr + // If this is the last reference, the connection will be destroyed + // CONNECTION_CLOSE is caller's responsibility +} + +score::Result CryptoStackImpl::CreateCryptoContext() +{ + // Create a context with the established connection. + // The connection is managed by the stack and was established during CreateCryptoStack. + + if (!m_connection) + { + std::cerr << "[API][CryptoStackImpl] ERROR: Connection is not initialized\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kConnectionFailed, "Connection is not initialized")}; + } + + auto ctx = std::make_unique(m_connection); + return ctx; +} + +score::Result CryptoStackImpl::GetMemoryAllocator() +{ + // TODO: Implement shared-memory allocator for data-plane zero-copy path. + // For now, this is not needed by the hash example test. + std::cerr << "[API][CryptoStackImpl] ERROR: GetMemoryAllocator not yet implemented\n"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kInternalError, "GetMemoryAllocator not yet implemented")}; +} + +} // namespace crypto +} // namespace mw +} // namespace score diff --git a/score/mw/crypto/api/src/crypto_stack_impl.hpp b/score/mw/crypto/api/src/crypto_stack_impl.hpp new file mode 100644 index 0000000..e05ab8c --- /dev/null +++ b/score/mw/crypto/api/src/crypto_stack_impl.hpp @@ -0,0 +1,64 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_SRC_CRYPTO_STACK_IMPL_HPP +#define SCORE_MW_CRYPTO_API_SRC_CRYPTO_STACK_IMPL_HPP + +#include "score/mw/crypto/api/crypto_stack_factory.hpp" +#include "score/mw/crypto/api/i_crypto_stack.hpp" + +#include "score/crypto/api/control_plane/i_connection.hpp" + +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +/// @brief Concrete ICryptoStack implementation connected to the crypto daemon. +/// +/// Each instance is associated with a single connection. +/// Construction receives the established connection; destruction releases +/// all associated resources including the node. +class CryptoStackImpl final : public ICryptoStack +{ + public: + /// @brief Constructs a crypto stack with an established connection. + /// @param config Stack configuration with connection endpoint + /// @param connection Established connection to the crypto daemon (with DataNodeId already set) + explicit CryptoStackImpl(const CryptoStackConfig& config, + std::shared_ptr connection); + + ~CryptoStackImpl() override; + + CryptoStackImpl(const CryptoStackImpl&) = delete; + CryptoStackImpl& operator=(const CryptoStackImpl&) = delete; + CryptoStackImpl(CryptoStackImpl&&) = delete; + CryptoStackImpl& operator=(CryptoStackImpl&&) = delete; + + // -- ICryptoStack -- + score::Result CreateCryptoContext() override; + score::Result GetMemoryAllocator() override; + + private: + CryptoStackConfig m_config; + std::shared_ptr m_connection; +}; + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_SRC_CRYPTO_STACK_IMPL_HPP diff --git a/score/mw/crypto/api/src/provider_type_converter.hpp b/score/mw/crypto/api/src/provider_type_converter.hpp new file mode 100644 index 0000000..cbe388d --- /dev/null +++ b/score/mw/crypto/api/src/provider_type_converter.hpp @@ -0,0 +1,56 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#ifndef SCORE_MW_CRYPTO_API_SRC_PROVIDER_TYPE_CONVERTER_HPP +#define SCORE_MW_CRYPTO_API_SRC_PROVIDER_TYPE_CONVERTER_HPP + +#include "score/mw/crypto/api/common/types.hpp" + +#include + +namespace score +{ +namespace mw +{ +namespace crypto +{ + +namespace ProviderTypeConverter +{ + +/// @brief Encode a client-side ProviderType preference as its IPC wire value. +/// +/// The wire protocol carries the raw uint8_t representation of the ProviderType +/// enumerator. The daemon decodes this with its own FromWireProviderType() +/// function and maps it to its internal CryptoProviderType classification. +/// +/// This function must NOT depend on daemon-internal headers — the client library +/// and the daemon are independently deployable components. +/// +/// Wire encoding (matches ProviderType enumerator positions): +/// 0 = kDefault +/// 1 = kHardware +/// 2 = kSoftware +/// 3 = kHardwarePreferred +/// 4 = kSoftwarePreferred +inline constexpr std::uint8_t ToWireValue(ProviderType api_type) noexcept +{ + return static_cast(api_type); +} + +} // namespace ProviderTypeConverter + +} // namespace crypto +} // namespace mw +} // namespace score + +#endif // SCORE_MW_CRYPTO_API_SRC_PROVIDER_TYPE_CONVERTER_HPP diff --git a/src/BUILD b/src/BUILD deleted file mode 100644 index e69de29..0000000 diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..53d5766 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,57 @@ +# Tests + +## Test Structure + +| Folder | Type | Framework | Description | +|---|---|---|---| +| `demo/` | Demo / manual | Google Test | Multi-provider demo binaries (e.g. MAC with SoftHSM + OpenSSL) | +| `grpc_control_plane/` | Component | Google Test | gRPC-based control-plane protocol tests | +| `integration_tests/` | Integration | Google Test + pytest | End-to-end daemon tests running inside Docker containers | +| `key_management/` | Component | Google Test | Key config, access policy, key store, OpenSSL/PKCS#11 key handlers | +| `openssl/` | Component | Google Test | OpenSSL block-cipher operation tests | +| `provider_test/` | Component | Google Test | Provider construction, PKCS#11 provider, multi-token isolation | +| `softhsm/` | Integration | Google Test | SoftHSM-specific token and session tests | +| `test_vectors/` | Data | — | Known-answer test vectors (HMAC, AES, etc.) | +| `utility/` | Component | Google Test | Shared test utilities and helpers | + +### Unit tests (Bazel) + +```sh +bazel test //tests/... +``` + +### Integration tests (Docker + pytest) + +The integration tests run the full daemon inside a Docker container with +SoftHSM configured. + +```sh +# From the workspace root: +cd tests/integration_tests +pytest integration_test.py.py -v +``` + +#### SoftHSM token initialisation + +The `init_softhsm_token` binary initialises a SoftHSM token and optionally +imports a pre-shared key: + +```sh +# Initialise token only: +./init_softhsm_token --label "MyToken" --pin 1234 --so-pin 12345678 + +# Initialise token and import an HMAC key: +./init_softhsm_token --label "MyToken" --pin 1234 --so-pin 12345678 \ + --import-key-file /path/to/key.bin \ + --import-key-label "hmac_key" \ + --import-key-type GENERIC_SECRET +``` + +### Multi-token isolation test + +```sh +bazel test //tests/provider_test:test_pkcs11_multi_token +``` + +This test validates that two SoftHSM tokens configured in the same process +maintain independent session pools and login state. diff --git a/tests/cpp/BUILD b/tests/cpp/BUILD deleted file mode 100644 index e3b354e..0000000 --- a/tests/cpp/BUILD +++ /dev/null @@ -1,19 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* -cc_test( - name = "cpp_test_main", - srcs = ["test_main.cpp"], - deps = [ - "@googletest//:gtest_main", # GoogleTest dependency via Bazel Modules - ], -) diff --git a/tests/cpp/test_main.cpp b/tests/cpp/test_main.cpp deleted file mode 100644 index 4d14df3..0000000 --- a/tests/cpp/test_main.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -#include - -// Function to be tested -int add(int a, int b) { - return a + b; -} - -// Test case -TEST(AdditionTest, HandlesPositiveNumbers) { - EXPECT_EQ(add(2, 3), 5); - EXPECT_EQ(add(10, 20), 30); -} - -TEST(AdditionTest, HandlesNegativeNumbers) { - EXPECT_EQ(add(-2, -3), -5); - EXPECT_EQ(add(-10, 5), -5); -} - -TEST(AdditionTest, HandlesZero) { - EXPECT_EQ(add(0, 0), 0); - EXPECT_EQ(add(0, 5), 5); - EXPECT_EQ(add(5, 0), 5); -} - -// Main function for running tests -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tests/demo/BUILD b/tests/demo/BUILD new file mode 100644 index 0000000..4f16bc4 --- /dev/null +++ b/tests/demo/BUILD @@ -0,0 +1,29 @@ +# ============================================================================= +# C O P Y R I G H T +# ============================================================================= +# Copyright (c) 2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:cc_test.bzl", "cc_test") + +cc_test( + name = "mac_multi_provider_demo", + srcs = ["mac_multi_provider_demo.cpp"], + data = [ + "//tests/test_vectors/key_management:key_management_test_vectors", + ], + deps = [ + "//score/crypto/daemon/key_management", + "//score/crypto/daemon/provider:provider_headers", + "//score/crypto/daemon/provider/score_provider/openssl:provider_openssl_library", + "//score/crypto/daemon/provider/score_provider/operations/mac:score_mac_handler", + "//third_party/openssl", + "@googletest//:gtest_main", + ], +) diff --git a/tests/demo/mac_multi_provider_demo.cpp b/tests/demo/mac_multi_provider_demo.cpp new file mode 100644 index 0000000..4226275 --- /dev/null +++ b/tests/demo/mac_multi_provider_demo.cpp @@ -0,0 +1,337 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +/// @file mac_multi_provider_demo.cpp +/// @brief Demonstrates multi-provider HMAC-SHA256 using the daemon-internal MAC pipeline. +/// +/// ## What this demo shows +/// +/// 1. **Capability inspection**: Verify that the OpenSSL provider returns +/// non-null from `GetCryptoHandlerFactory()` and `GetKeyHandler()` — +/// the MISRA-safe capability query pattern (no dynamic_cast / RTTI). +/// +/// 2. **Guard path (ephemeral key)**: Generate an in-memory HMAC-SHA256 key +/// with `IKeyHandler::GenerateKey()`, pass it to `MakeMacHandler()` via +/// the key opaque ID and key size, compute HMAC over a test +/// message, then verify. The key is embedded in `InitializeContext()` — +/// matching the hash InitializeContext pattern, no separate init-key step. +/// +/// 3. **Slot-direct path (file-backed key)**: Load a persistent key from +/// `tests/test_vectors/key_management/hmac_sha256.key` through +/// `FileBackedSlotHandler::LoadKey()`, compute HMAC, and verify that the +/// result matches when re-computed with the same key. +/// +/// 4. **Verification**: Both paths demonstrate MAC_UPDATE → MAC_FINAL → +/// VerifyMac(), exercising the full `OpenSslHmacHandler` dispatch table. +/// The ProviderManager test shows the same direct virtual method +/// pattern used by the daemon. +/// +/// ## PKCS#11 path +/// +/// The PKCS#11 / SoftHSM path is demonstrated conditionally: if the SOFTHSM +/// provider initialises successfully (i.e. a test token is present), a third +/// test case runs HMAC-SHA256 via `Pkcs11MacHandler`. If SoftHSM is unavailable, +/// that test is skipped with a clear message. +/// +/// ## Building +/// bazel test //tests/demo:mac_multi_provider_demo + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Provider interfaces +#include "score/crypto/daemon/provider/i_provider.hpp" +#include "score/crypto/daemon/provider/provider_manager.hpp" + +// Daemon config +#include "score/crypto/daemon/config/inc/config.hpp" + +// OpenSSL provider +#include "score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.hpp" +#include "score/crypto/daemon/provider/score_provider/openssl/provider_openssl.hpp" +#include "score/crypto/daemon/provider/score_provider/operations/mac/mac_executor.hpp" + +// Key management +#include "score/crypto/daemon/key_management/interfaces/i_key_factory.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_handler.hpp" +#include "score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp" +#include "score/crypto/daemon/key_management/interfaces/key_slot_config.hpp" +#include "score/crypto/daemon/key_management/slot/file_backed_slot_handler.hpp" + +// MAC handler interface +#include "score/crypto/daemon/provider/score_provider/operations/mac/score_mac_handler.hpp" + +#include + +using namespace score::crypto::daemon; +using namespace score::crypto::daemon::provider; +using namespace score::crypto::daemon::provider::score_provider::operations::mac; +using namespace score::crypto::daemon::key_management; +using namespace score::crypto::daemon::provider::score_provider::openssl; +using namespace score::crypto::daemon::provider::score_provider::openssl::handler; + +namespace km = score::crypto::daemon::key_management; +namespace common = score::crypto::daemon::common; +using InitializationParams = ::score::crypto::daemon::provider::handler::InitializationParams; + +// ============================================================================ +// Helpers +// ============================================================================ + +static std::string hex(const uint8_t* data, std::size_t len) +{ + std::ostringstream ss; + ss << std::hex << std::setfill('0'); + for (std::size_t i = 0; i < len; ++i) + { + ss << std::setw(2) << static_cast(data[i]); + } + return ss.str(); +} + +static constexpr const char* kTestMessage = "Score multi-provider crypto demo"; + +// ============================================================================ +// Test Fixture — sets up an in-memory OpenSSL key infrastructure +// ============================================================================ +class MacDemoTest : public ::testing::Test +{ + protected: + static void SetUpTestSuite() + { + // Create and initialise the OpenSSL provider once for all tests. + m_provider = std::make_shared(); + provider::ProviderInitContext ctx{0, "OPENSSL"}; // ID 0, name "OPENSSL" + ASSERT_TRUE(m_provider->Initialize(ctx)) << "OpenSSL provider initialisation failed"; + + // Key factory (IKeyFactory) for key generation/import. + m_key_factory = m_provider->GetKeyFactory(); + ASSERT_NE(m_key_factory, nullptr); + } + + static void TearDownTestSuite() + { + if (m_provider) + { + m_provider->Shutdown(); + m_provider.reset(); + } + } + + /// @brief Create a fresh `OpenSslHmacHandler` optionally bound to a key. + /// + /// When @p key_handler is non-null it is provided via InitializationParams + /// so the handler transitions directly to STREAM_INIT state (ready to compute MAC). + std::shared_ptr MakeMacHandler(const km::IKeyHandler* key_handler = nullptr) + { + auto executor = std::make_unique(); + auto h = std::make_shared(std::move(executor), "HMAC-SHA256"); + InitializationParams init_params{}; + if (key_handler != nullptr) + { + init_params.bound_key_handler = key_handler; + } + EXPECT_TRUE(h->InitializeContext(init_params).has_value()) << "OpenSslHmacHandler::InitializeContext failed"; + return h; + } + + static std::shared_ptr m_provider; + static km::IKeyFactory::Sptr m_key_factory; + score::crypto::daemon::config::Config m_config; +}; + +// Static member initialization +std::shared_ptr MacDemoTest::m_provider; +km::IKeyFactory::Sptr MacDemoTest::m_key_factory; + +// ============================================================================ +// Demo 1 — Capability inspection +// ============================================================================ +TEST_F(MacDemoTest, Demo1_ProviderCapabilityInspection) +{ + std::cout << "\n=== Demo 1: Capability Inspection (MISRA-safe, no dynamic_cast) ===\n"; + + // OpenSSL should support crypto operations. + auto factory = m_provider->GetCryptoHandlerFactory(); + ASSERT_NE(factory, nullptr) << "OpenSSL does not support crypto operations"; + std::cout << "[PASS] GetCryptoHandlerFactory() returned a non-null factory\n"; + + // OpenSSL should support key management. + auto key_factory = m_provider->GetKeyFactory(); + ASSERT_NE(key_factory, nullptr) << "OpenSSL does not support key management"; + std::cout << "[PASS] GetKeyFactory() returned a non-null factory\n"; + + // Provider capabilities are now reflected by the methods it exposes, not by + // a capability bitmask. GetKeyFactory() != nullptr confirms key management support. + + // Verify MAC handler can be created. + auto mac_handler = factory->CreateHandler("MAC", "HMAC-SHA256"); + ASSERT_TRUE(mac_handler.has_value()) << "Factory does not support MAC/HMAC-SHA256"; + std::cout << "[PASS] Factory supports MAC/HMAC-SHA256\n"; +} + +// ============================================================================ +// Demo 2 — Guard path: ephemeral key + MAC +// ============================================================================ +TEST_F(MacDemoTest, Demo2_EphemeralKeyMac) +{ + std::cout << "\n=== Demo 2: Guard Path — Ephemeral Key ===\n"; + + // 2a. Generate an ephemeral HMAC-SHA256 key. + KeyGenerationRequest gen_req{}; + gen_req.algorithm = "HMAC-SHA256"; + gen_req.permissions = score::mw::crypto::KeyOperationPermission::kAll; // includes kExport for demo verification + + auto gen_result = m_key_factory->GenerateKey(gen_req); + ASSERT_TRUE(gen_result.has_value()) << "GenerateKey failed"; + auto& key_handler = gen_result.value(); + const auto& key_handle = key_handler->GetHandle(); + std::cout << "[PASS] Generated ephemeral HMAC-SHA256 key (opaque_id=" << key_handle.opaque_id + << ", size=" << key_handle.key_size << " B)\n"; + + // 2b. Create MAC handler and bind the key. + auto mac = MakeMacHandler(key_handler.get()); + std::cout << "[PASS] MAC handler bound to ephemeral key via InitializeContext\n"; + + // 2b.5. Initialize the MAC context with the bound key. + auto init_result = mac->StartMac(std::nullopt); + ASSERT_TRUE(init_result.has_value()) << "StartMac failed"; + + // 2c. Compute MAC over the test message. + const auto* msg = reinterpret_cast(kTestMessage); + const std::size_t msg_len = std::strlen(kTestMessage); + + auto update_result = mac->UpdateMac(common::VirtualMemoryBufferConst{msg, msg_len}); + ASSERT_TRUE(update_result.has_value()) << "UpdateMac failed"; + std::cout << "[PASS] UpdateMac succeeded\n"; + + // 2d. Finalize and get the MAC tag. + auto final_result = mac->FinalizeMac(std::nullopt, std::nullopt); // Handler allocates buffer + ASSERT_TRUE(final_result.has_value()) << "FinalizeMac failed"; + + // Extract the OwnedBuffer from the ResponseParameters variant + const auto& response = final_result.value(); + ASSERT_EQ(response.size(), 1U) << "Expected single response parameter"; + const auto& param = response[0]; + ASSERT_TRUE(std::holds_alternative(param)) << "Expected OwnedBuffer in response"; + const auto& tag = std::get(param); + ASSERT_EQ(tag.size(), 32U) << "Expected 32-byte HMAC-SHA256 tag"; + std::cout << "[PASS] FinalizeMac succeeded. Tag (hex): " << hex(tag.data(), tag.size()) << "\n"; + + // Cleanup. + static_cast(key_handler->Release()); +} + +// ============================================================================ +// Demo 3 — Slot-direct path: file-backed key +// ============================================================================ +TEST_F(MacDemoTest, Demo3_SlotDirectFileBackedKey) +{ + std::cout << "\n=== Demo 3: Slot-Direct Path — File-Backed Key ===\n"; + + // 3a. Build a slot config for the test HMAC key on disk. + KeySlotConfig slot{}; + slot.slot_name = "demo/sw-hmac-256"; + slot.algorithm = "HMAC-SHA256"; + // Config-time: populate provider names; runtime would populate provider_ids via ResolveProviderIds + slot.provider_names = {common::kProviderNameOpenSSL}; + slot.provider_ids = {0}; // 0 = OpenSSL (typical registration order) + // Write a temporary deployment descriptor pointing to the test key file. + const std::string deploy_path = + std::string{std::filesystem::temp_directory_path().string()} + "/demo_sw_hmac256_slot.kv"; + { + std::ofstream f(deploy_path); + f << "[key]\n" + << std::string{km::deployment_keys::kKeyPath} << "=tests/test_vectors/key_management/hmac_sha256.key\n"; + } + slot.deployment_path = deploy_path; + slot.deployment_format = "kv"; + + // 3b. Load the key from the file. + FileBackedSlotHandler slot_handler(m_key_factory); + auto load_result = slot_handler.LoadKey(slot); + ASSERT_TRUE(load_result.has_value()) + << "LoadKey failed: " << static_cast(load_result.error()) + << "\n (Ensure tests are run from the workspace root so the key file is accessible)"; + + auto& key_handler = load_result.value(); + const auto& key_handle = key_handler->GetHandle(); + std::cout << "[PASS] Loaded file-backed HMAC-SHA256 key (opaque_id=" << key_handle.opaque_id + << ", size=" << key_handle.key_size << " B)\n"; + + // 3c. MAC compute and finalize. + auto mac = MakeMacHandler(key_handler.get()); + + // Initialize the MAC context with the bound key. + auto init_result = mac->StartMac(std::nullopt); + ASSERT_TRUE(init_result.has_value()) << "StartMac failed"; + + const auto* msg = reinterpret_cast(kTestMessage); + const std::size_t msg_len = std::strlen(kTestMessage); + ASSERT_TRUE(mac->UpdateMac(common::VirtualMemoryBufferConst{msg, msg_len}).has_value()) << "UpdateMac failed"; + + auto final_result = mac->FinalizeMac(std::nullopt, std::nullopt); + ASSERT_TRUE(final_result.has_value()) << "FinalizeMac failed"; + + const auto& response = final_result.value(); + ASSERT_EQ(response.size(), 1U) << "Expected single response parameter"; + const auto& param = response[0]; + ASSERT_TRUE(std::holds_alternative(param)) << "Expected OwnedBuffer in response"; + const auto& tag = std::get(param); + ASSERT_EQ(tag.size(), 32U) << "Expected 32-byte HMAC-SHA256 tag"; + std::cout << "[PASS] MAC computed for file-backed key. Tag (hex): " << hex(tag.data(), tag.size()) << "\n"; + + static_cast(key_handler->Release()); +} + +// ============================================================================ +// Demo 4 — Direct IProvider capability pattern +// ============================================================================ +TEST_F(MacDemoTest, Demo4_ProviderDirectCapabilityPattern) +{ + std::cout << "\n=== Demo 4: Direct IProvider Capability Pattern (MISRA-safe) ===\n"; + + // Simulate the ProviderManager lookup used in the real daemon. + ProviderManager mgr(m_config); + mgr.RegisterProvider("OPENSSL", m_provider, common::CryptoProviderType::SOFTWARE); + + // Retrieve the provider via ProviderManager. + auto provider = mgr.GetProvider(m_provider->GetProviderId()); + ASSERT_NE(provider, nullptr); + std::cout << "[PASS] GetProvider(\"OPENSSL\") returned non-null\n"; + + // Query capabilities directly — no dynamic_cast, no RTTI. + auto factory = provider->GetCryptoHandlerFactory(); + ASSERT_NE(factory, nullptr); + std::cout << "[PASS] GetCryptoHandlerFactory() returned non-null\n"; + + auto key_factory = provider->GetKeyFactory(); + ASSERT_NE(key_factory, nullptr); + std::cout << "[PASS] GetKeyFactory() returned non-null\n"; + + // Providers that don't support a capability return nullptr (e.g. a hash-only provider). + std::cout << "[INFO] Non-supported capabilities return nullptr — no exceptions, no RTTI.\n"; + + // Use the factory to create a MAC handler. + auto handler_res = factory->CreateHandler("MAC", "HMAC-SHA256"); + ASSERT_TRUE(handler_res.has_value()); + ASSERT_NE(handler_res.value(), nullptr); + std::cout << "[PASS] Factory created MAC handler via direct capability interface\n"; +} diff --git a/tests/grpc_control_plane/BUILD b/tests/grpc_control_plane/BUILD new file mode 100644 index 0000000..9fe62c1 --- /dev/null +++ b/tests/grpc_control_plane/BUILD @@ -0,0 +1,29 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_test") +load("@score_itf//:defs.bzl", "py_itf_test") + +cc_test( + name = "test_control_plane", + timeout = "short", + srcs = ["test_control_plane.cpp"], + dynamic_deps = ["//third_party/grpc:libgrpc_shared"], + deps = [ + "//score/crypto/api:operations", + "//score/crypto/api/control_plane:control_plane_impl", + "//score/crypto/daemon/control_plane", + "//score/crypto/daemon/mediator", + "//score/crypto/ipc/grpc_adapter:grpc_control_server", + "@googletest//:gtest", + ], +) diff --git a/tests/grpc_control_plane/test_control_plane.cpp b/tests/grpc_control_plane/test_control_plane.cpp new file mode 100644 index 0000000..94c2cda --- /dev/null +++ b/tests/grpc_control_plane/test_control_plane.cpp @@ -0,0 +1,226 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "score/crypto/api/control_plane/connection_factory.hpp" +#include "score/crypto/daemon/common/actors.hpp" +#include "score/crypto/daemon/config/inc/config.hpp" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/daemon/control_plane/src/connection_handler.hpp" +#include "score/crypto/daemon/data_manager/data_manager.hpp" +#include "score/crypto/ipc/grpc_adapter/grpc_control_server.h" +#include "score/mw/crypto/api/common/error_domain.hpp" + +// namespace aliases +namespace api = score::crypto::api; +namespace control_plane = score::crypto::daemon::control_plane; +namespace ipc = score::crypto::ipc; + +namespace score::crypto::test +{ +daemon::common::OperationActor dummyActorA = 0; +daemon::common::OperationActor dummyActorB = 1; +daemon::common::OperationAction dummyActionA = 0; +daemon::common::OperationAction dummyActionB = 1; + +std::uint64_t dummyReturnParameter = 12345; +std::string dummyStringParameter = "testData"; + +// Dummy handler node in chain - processes operations and returns proper responses +// Terminal handler (no next handler in test chain) +class DummyRequestHandlerNode : public score::crypto::daemon::control_plane::IRequestHandler +{ + public: + DummyRequestHandlerNode() = default; + + score::crypto::daemon::control_plane::protocol::ControlResponse processRequest( + const score::crypto::daemon::control_plane::protocol::ControlRequest& request) override + { + score::crypto::daemon::control_plane::protocol::ControlResponse response; + response.request_id = request.request_id; + + // Terminal handler - process operations and build proper responses + auto responseBuilder = score::crypto::daemon::control_plane::protocol::OperationResponseBuilder(); + + for (const auto& op : request.operation.operations) + { + const auto& opId = op.operationId; + + if (opId.operationActor == dummyActorA && opId.operationAction == dummyActionA) + { + if (op.parameters.size() != 2) + { + responseBuilder.operation(opId).return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + continue; + } + + auto paramRes = op.getParameter(0); + if (!paramRes.has_value()) + { + responseBuilder.operation(opId).return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + continue; + } + auto paramValue = paramRes.value(); + if (paramValue != dummyStringParameter) + { + responseBuilder.operation(opId).return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + continue; + } + + auto noParamRes = op.getParameter(1); + if (!noParamRes.has_value()) + { + responseBuilder.operation(opId).return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + continue; + } + + responseBuilder.operation(opId).return_success().return_value_uint64(dummyReturnParameter); + } + else + { + responseBuilder.operation(opId).return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + } + } + + response.operation = responseBuilder.build().value(); + return response; + } +}; + +// Test factory implementation for creating handler chains in tests +class TestRequestHandlerFactory : public score::crypto::daemon::control_plane::IHandlerChainFactory +{ + public: + TestRequestHandlerFactory(std::shared_ptr data_manager, + const score::crypto::daemon::config::Config& config) + : m_data_manager(std::move(data_manager)), m_config(config) + { + } + + std::unique_ptr CreateRequestHandler() override + { + // Create a fresh dummy handler for each request + auto dummy_handler = std::make_unique(); + return std::make_unique( + std::move(dummy_handler), m_data_manager, m_config); + } + + private: + std::shared_ptr m_data_manager; + const score::crypto::daemon::config::Config& m_config; +}; + +} // namespace score::crypto::test + +class ControlPlaneTest : public ::testing::Test +{ + protected: + void SetUp() override + { + // Namespace alias to avoid conflicts with unistd.h daemon() function + namespace ipc = score::crypto::ipc; + + // Generate unique socket path for this test + _socket_path = "/tmp/test_crypto_" + std::to_string(getpid()) + "_" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".sock"; + + // Create default config for testing + score::crypto::daemon::config::Config config; + + // Create data manager + auto data_manager = std::make_shared(); + + // Create test factory that produces ConnectionHandler wrappers with fresh dummy nodes per thread + auto test_factory = std::make_unique(data_manager, config); + + // Create gRPC server with factory (server will create ConnectionHandler wrappers per thread) + _server = std::make_unique(std::move(test_factory)); + + // Start gRPC server in background thread + _server_thread = std::thread([this]() { + _server->Start(_socket_path); + _server->WaitForTermination(); + }); + + // Give server time to start + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + void TearDown() override + { + if (_server) + { + _server->Stop(); + } + + if (_server_thread.joinable()) + { + _server_thread.join(); + } + } + + std::string _socket_path; + std::unique_ptr _server; + std::thread _server_thread; +}; + +TEST_F(ControlPlaneTest, Connection_SendRequest) +{ + auto endpoint = "unix://" + _socket_path; + auto connection_res = api::control_plane::ConnectionFactory().CreateConnection(endpoint); + ASSERT_TRUE(connection_res.has_value()); + auto connection = std::move(connection_res).value(); + + // Create context with algorithm first + auto controlRequest = score::crypto::daemon::control_plane::protocol::ControlRequestBuilder() + .forDataNodeId(0) + .operation({score::crypto::test::dummyActorA, score::crypto::test::dummyActionA}) + .with_in_string("testData") + .with_no_param() + .build(); + + if (!controlRequest.has_value()) + { + ASSERT_TRUE(false) << "Failed to build control request"; + } + + auto dummyResponseResult = connection->SendRequest(controlRequest.value()); + ASSERT_TRUE(dummyResponseResult.has_value()); + + auto dummyResponse = std::move(dummyResponseResult).value(); + ASSERT_EQ(dummyResponse.operation.operations.size(), 1); + ASSERT_EQ(dummyResponse.operation.operations[0].operationId.operationActor, score::crypto::test::dummyActorA); + ASSERT_EQ(dummyResponse.operation.operations[0].operationId.operationAction, score::crypto::test::dummyActionA); + ASSERT_EQ(dummyResponse.operation.operations[0].result, + score::crypto::daemon::control_plane::protocol::OPERATION_RESULT_SUCCESS); + ASSERT_EQ(dummyResponse.operation.operations[0].parameters.size(), 1); + auto returnParameterRes = dummyResponse.operation.operations[0].getParameter(0); + ASSERT_TRUE(returnParameterRes.has_value()); + auto returnParameter = returnParameterRes.value(); + ASSERT_EQ(returnParameter, score::crypto::test::dummyReturnParameter); +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/integration_tests/BUILD b/tests/integration_tests/BUILD new file mode 100644 index 0000000..b6c9b0c --- /dev/null +++ b/tests/integration_tests/BUILD @@ -0,0 +1,102 @@ +# ============================================================================= +# C O P Y R I G H T +# ----------------------------------------------------------------------------- +# Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +# +# The reproduction, distribution and utilization of this file as +# well as the communication of its contents to others without express +# authorization is prohibited. Offenders will be held liable for the +# payment of damages. All rights reserved in the event of the grant +# of a patent, utility model or design. +# ============================================================================= + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_test") +load("@score_itf//:defs.bzl", "py_itf_test") + +cc_binary( + name = "control_client_app", + srcs = ["control_client_app.cpp"], + dynamic_deps = ["//third_party/grpc:libgrpc_shared"], + deps = [ + "//score/crypto/api:operations", + "//score/crypto/api/control_plane:control_plane_impl", + "@googletest//:gtest", + ], +) + +cc_binary( + name = "init_softhsm_token", + srcs = ["init_softhsm_token.cpp"], + deps = [ + "//third_party/soft_hsm:libsofthsm_shared", + ], +) + +cc_binary( + name = "score_api_hash_example", + srcs = ["score_api_hash_example.cpp"], + data = [ + "//tests/test_vectors/config:integration_test_config", + "//tests/test_vectors/hash:hash_test_vectors", + ], + dynamic_deps = ["//third_party/grpc:libgrpc_shared"], + deps = [ + "//score/mw/crypto/api:crypto_stack", + "//tests/utility", + "@googletest//:gtest", + ], +) + +cc_binary( + name = "score_api_mac_example", + srcs = ["score_api_mac_example.cpp"], + data = [ + "//tests/test_vectors/config:integration_test_config", + "//tests/test_vectors/mac:mac_test_vectors", + ], + dynamic_deps = ["//third_party/grpc:libgrpc_shared"], + deps = [ + "//score/mw/crypto/api:crypto_stack", + "//tests/utility", + "@googletest//:gtest", + ], +) + +cc_binary( + name = "score_demo", + srcs = ["score_demo.cpp"], + data = [ + "//tests/test_vectors/config:integration_test_config", + "//tests/test_vectors/mac:mac_test_vectors", + ], + dynamic_deps = ["//third_party/grpc:libgrpc_shared"], + deps = [ + "//score/mw/crypto/api:crypto_stack", + "//tests/utility", + ], +) + +py_itf_test( + name = "integration_test_docker", + size = "small", + srcs = ["integration_test.py"], + args = [ + "--docker-image=ubuntu:24.04", + "--log-level=WARNING", + ], + data = [ + ":control_client_app", + ":init_softhsm_token", + ":score_api_hash_example", + ":score_api_mac_example", + ":score_demo", + "//score/crypto/api/control_plane", + "//score/crypto/daemon:crypto_daemon", + "//third_party/grpc:libgrpc_shared", + "//third_party/openssl:openssl_shared", + "//third_party/soft_hsm:soft_hsm_cmake", + ], + plugins = [ + "itf.plugins.docker", + ], +) diff --git a/tests/integration_tests/DEMO.md b/tests/integration_tests/DEMO.md new file mode 100644 index 0000000..a0b8852 --- /dev/null +++ b/tests/integration_tests/DEMO.md @@ -0,0 +1,37 @@ +# Steps to run demo without bazel (integration environment) + +## Prepare Environment + +``` +bazel build //score/... //tests/... + +mkdir -p /opt/crypto/tests/test_vectors/config +mkdir -p /opt/crypto/deploy +cp -r ./tests/test_vectors /opt/crypto/tests/ +cp -r bazel-bin/tests/test_vectors /opt/crypto/tests/ +cp -r tests/test_vectors/config/*.kv /opt/crypto/deploy + +./bazel-bin/tests/integration_tests/init_softhsm_token \ + --token-dir /tmp/softhsm_tokens \ + --config-path /tmp/softhsm2.conf \ + --token-label SoftHSM \ + --so-pin 12345678 \ + --user-pin 1234 \ + --import-key-file /opt/crypto/tests/test_vectors/mac/key_aes_256.key \ + --import-key-label integration_test_hmac +``` + +## Start Daemon + +``` +export CRYPTO_CONFIG_FILE=/opt/crypto/tests/test_vectors/config/integration_test_config.bin +export SOFTHSM2_CONF=/tmp/softhsm2.conf + +./bazel-bin/score/crypto/daemon/crypto_daemon +``` + +## Start Demo + +``` +./bazel-bin/tests/integration_tests/score_demo +``` diff --git a/tests/integration_tests/control_client_app.cpp b/tests/integration_tests/control_client_app.cpp new file mode 100644 index 0000000..a69fa8e --- /dev/null +++ b/tests/integration_tests/control_client_app.cpp @@ -0,0 +1,587 @@ +// ============================================================================= +// C O P Y R I G H T +// ----------------------------------------------------------------------------- +// Copyright (c) 2025-2026 by ETAS GmbH. All rights reserved. +// +// The reproduction, distribution and utilization of this file as +// well as the communication of its contents to others without express +// authorization is prohibited. Offenders will be held liable for the +// payment of damages. All rights reserved in the event of the grant +// of a patent, utility model or design. +// ============================================================================= + +#include "score/crypto/api/control_plane/connection_factory.hpp" +#include "score/crypto/api/control_plane/i_connection.hpp" +#include "score/crypto/common/types.hpp" +#include "score/crypto/daemon/common/actors.hpp" +#include "score/crypto/daemon/control_plane/control_operations.h" +#include "score/crypto/daemon/control_plane/control_protocol.h" +#include "score/crypto/daemon/mediator/mediator_operations.hpp" +#include "score/crypto/daemon/provider/handler/operations/hash_handler_operations.hpp" +#include "score/crypto/ipc/ipc_config.h" +#include "score/mw/crypto/api/common/error_domain.hpp" +#include +#include +#include +#include +#include + +namespace score::crypto::test +{ + +// Simple barrier implementation for synchronizing threads +class Barrier +{ + public: + explicit Barrier(int count) : threshold_(count), count_(count), generation_(0) {} + + void Wait() + { + std::unique_lock lock(mutex_); + int gen = generation_; + if (--count_ == 0) + { + generation_++; + count_ = threshold_; + cv_.notify_all(); + } + else + { + cv_.wait(lock, [this, gen] { + return gen != generation_; + }); + } + } + + private: + std::mutex mutex_; + std::condition_variable cv_; + int threshold_; + int count_; + int generation_; +}; + +// ============================================================================ +// Helper functions for common operations +// ============================================================================ + +/// Create a control plane connection and open a connection on the daemon +inline score::crypto::Expected, std::uint64_t>, + score::mw::crypto::CryptoErrorCode> +CreateConnectionWithOpen() +{ + auto endpoint = "unix://" + std::string(score::crypto::ipc::kControlSocket); + api::control_plane::ConnectionFactory factory; + auto connResult = factory.CreateConnection(endpoint); + + if (!connResult.has_value()) + { + return score::crypto::make_unexpected(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + } + + auto connection = std::move(connResult.value()); + + // Send CONNECTION_OPEN to the daemon + auto connOpenResponse = daemon::control_plane::protocol::ControlRequestBuilder() + .forDataNodeId(0) + .operation(daemon::control_plane::operations::OpenConnection()) + .build(); + + if (!connOpenResponse.has_value()) + { + return score::crypto::make_unexpected(score::mw::crypto::CryptoErrorCode::kInternalError); + } + + auto connOpenResponseRes = connection->SendRequest(connOpenResponse.value()); + auto connOpenValidator = daemon::control_plane::protocol::ControlResponseValidator::FromResult(connOpenResponseRes); + + if (!connOpenValidator.isValid()) + { + return score::crypto::make_unexpected(score::mw::crypto::CryptoErrorCode::kInternalError); + } + + connOpenValidator.expectOperation(daemon::control_plane::operations::OpenConnection()).expectSuccess(); + + if (!connOpenValidator.isValid()) + { + return score::crypto::make_unexpected(score::mw::crypto::CryptoErrorCode::kInternalError); + } + + auto connId = connOpenValidator.getParameterAt(0, 0); + if (!connId.has_value()) + { + return score::crypto::make_unexpected(score::mw::crypto::CryptoErrorCode::kInternalError); + } + + return std::make_pair(std::move(connection), connId.value()); +} + +/// Create a context and return the context_id +inline score::crypto::Expected +CreateContext(api::control_plane::IConnection* connection, std::uint64_t connection_id, const std::string& algorithm) +{ + auto ctxResponse = daemon::control_plane::protocol::ControlRequestBuilder() + .forDataNodeId(connection_id) + .operation(daemon::mediator::operations::CreateContext()) + .with_in_string("HASH") + .with_in_string(algorithm) + .build(); + + if (!ctxResponse.has_value()) + { + return score::crypto::make_unexpected(score::mw::crypto::CryptoErrorCode::kContextCreationFailed); + } + + auto ctxResponseRes = connection->SendRequest(ctxResponse.value()); + auto ctxValidator = daemon::control_plane::protocol::ControlResponseValidator::FromResult(ctxResponseRes); + + if (!ctxValidator.isValid()) + { + return score::crypto::make_unexpected(score::mw::crypto::CryptoErrorCode::kContextCreationFailed); + } + + ctxValidator.expectOperation(daemon::mediator::operations::CreateContext()).expectSuccess(); + + if (!ctxValidator.isValid()) + { + return score::crypto::make_unexpected(score::mw::crypto::CryptoErrorCode::kContextCreationFailed); + } + + auto ctxId = ctxValidator.getParameterAt(0, 0); + if (!ctxId.has_value()) + { + return score::crypto::make_unexpected(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + } + + return ctxId.value(); +} + +/// Close a context +inline bool CloseContext(api::control_plane::IConnection* connection, std::uint64_t context_id) +{ + auto closeCtxResponse = daemon::control_plane::protocol::ControlRequestBuilder() + .forDataNodeId(context_id) + .operation(daemon::mediator::operations::CloseContext()) + .build(); + + if (!closeCtxResponse.has_value()) + { + return false; + } + + auto closeCtxResponseRes = connection->SendRequest(closeCtxResponse.value()); + auto closeCtxValidator = daemon::control_plane::protocol::ControlResponseValidator::FromResult(closeCtxResponseRes); + + if (!closeCtxValidator.isValid()) + { + return false; + } + + closeCtxValidator.expectOperation(daemon::mediator::operations::CloseContext()).expectSuccess(); + + return closeCtxValidator.isValid(); +} + +/// Close a connection +inline bool CloseConnection(api::control_plane::IConnection* connection, std::uint64_t connection_id) +{ + auto closeConnResponse = daemon::control_plane::protocol::ControlRequestBuilder() + .forDataNodeId(connection_id) + .operation(daemon::control_plane::operations::CloseConnection()) + .build(); + + if (!closeConnResponse.has_value()) + { + return false; + } + + auto closeConnResponseRes = connection->SendRequest(closeConnResponse.value()); + auto closeConnValidator = + daemon::control_plane::protocol::ControlResponseValidator::FromResult(closeConnResponseRes); + + return closeConnValidator.isValid(); +} + +// Parameterized test data structure +struct HashTestData +{ + std::string algorithm; + std::string input_data; + std::vector expected_hash; + size_t expected_size; + + std::string GetTestName() const + { + std::string name = algorithm + "_" + input_data; + // Replace all non-alphanumeric characters (except underscore) with underscore + for (auto& c : name) + { + if (!std::isalnum(static_cast(c)) && c != '_') + { + c = '_'; + } + } + // Handle empty input_data case + if (input_data.empty()) + { + name = algorithm + "_empty_string"; + } + return name; + } +}; + +class ParameterizedHashTest : public ::testing::TestWithParam +{ + protected: + void SetUp() override + { + auto conn_result = CreateConnectionWithOpen(); + ASSERT_TRUE(conn_result.has_value()) << "Failed to create connection"; + auto pair = std::move(conn_result).value(); + _connection = std::move(pair.first); + _connection_id = pair.second; + } + + std::unique_ptr _connection; + std::uint64_t _connection_id; +}; + +TEST_P(ParameterizedHashTest, HashWithDifferentAlgorithmsAndData) +{ + auto test_data = GetParam(); + + // 1. Create context with CTX_CREATE and specify algorithm + auto ctxIdRes = CreateContext(_connection.get(), _connection_id, test_data.algorithm); + ASSERT_TRUE(ctxIdRes.has_value()) << "Failed to create context"; + uint64_t context_id = ctxIdRes.value(); + std::cout << "Created context with context_id: " << context_id << " using algorithm: " << test_data.algorithm + << std::endl; + + // 2. Prepare input data for hashing + std::vector inputBuffer(test_data.input_data.begin(), test_data.input_data.end()); + + // 3. Build HASH_SS operation request with context_id and data + auto operationResponse = daemon::control_plane::protocol::ControlRequestBuilder() + .forDataNodeId(context_id) + .operation({daemon::common::actors::OP_ACTOR_HASH_HANDLER, + daemon::provider::handler::hash_handler_operations::HASH_SS}) + .with_in_data_buffer(inputBuffer) + .build(); + + if (!operationResponse.has_value()) + { + // Close the context first, then assert + CloseContext(_connection.get(), context_id); + ASSERT_TRUE(false) << "Failed to build HASH_SS request"; + } + + // Request operation through the connection + auto responseRes = _connection->SendRequest(operationResponse.value()); + auto hashValidator = daemon::control_plane::protocol::ControlResponseValidator::FromResult(responseRes); + ASSERT_TRUE(hashValidator.isValid()) << hashValidator.getError(); + + hashValidator + .expectOperation({daemon::common::actors::OP_ACTOR_HASH_HANDLER, + daemon::provider::handler::hash_handler_operations::HASH_SS}) + .expectSuccess(); + + ASSERT_TRUE(hashValidator.isValid()) << hashValidator.getError(); + + auto hashOutput = hashValidator.getParameterAt(0, 0); + ASSERT_TRUE(hashOutput.has_value()) << "Failed to extract hash output from response"; + + ASSERT_EQ(hashOutput.value().size(), test_data.expected_size) + << test_data.algorithm << " should produce " << test_data.expected_size << " bytes"; + + // Verify hash matches expected value + ASSERT_EQ(hashOutput.value(), test_data.expected_hash) + << "Hash output does not match expected " << test_data.algorithm << " for '" << test_data.input_data << "'"; + + // 4. Close the context + ASSERT_TRUE(CloseContext(_connection.get(), context_id)) << "Failed to close context"; + + // 5. Close the connection + ASSERT_TRUE(CloseConnection(_connection.get(), _connection_id)) << "Failed to close connection"; +} + +INSTANTIATE_TEST_SUITE_P( + HashAlgorithmsAndData, + ParameterizedHashTest, + ::testing::Values( + // SHA256 test cases + HashTestData{"SHA256", + "Hello, World!", + {0xdf, 0xfd, 0x60, 0x21, 0xbb, 0x2b, 0xd5, 0xb0, 0xaf, 0x67, 0x62, 0x90, 0x80, 0x9e, 0xc3, 0xa5, + 0x31, 0x91, 0xdd, 0x81, 0xc7, 0xf7, 0x0a, 0x4b, 0x28, 0x68, 0x8a, 0x36, 0x21, 0x82, 0x98, 0x6f}, + 32}, + HashTestData{"SHA256", + "Hello S-Core", // Empty string + {0xcf, 0x4a, 0x68, 0x50, 0x44, 0x51, 0x2f, 0xbc, 0xb1, 0x08, 0xeb, 0x37, 0x25, 0x48, 0x5b, 0x61, + 0x02, 0x6f, 0x7d, 0xb4, 0x2b, 0x70, 0xef, 0x78, 0xee, 0x4f, 0x96, 0x96, 0x23, 0x17, 0x45, 0x25}, + 32}, + HashTestData{"SHA256", + "The quick brown fox jumps over the lazy dog", + {0xd7, 0xa8, 0xfb, 0xb3, 0x07, 0xd7, 0x80, 0x94, 0x69, 0xca, 0x9a, 0xbc, 0xb0, 0x08, 0x2e, 0x4f, + 0x8d, 0x56, 0x51, 0xe4, 0x6d, 0x3c, 0xdb, 0x76, 0x2d, 0x02, 0xd0, 0xbf, 0x37, 0xc9, 0xe5, 0x92}, + 32}, + // SHA512 test cases + HashTestData{"SHA512", + "Hello, World!", + {0x37, 0x4d, 0x79, 0x4a, 0x95, 0xcd, 0xcf, 0xd8, 0xb3, 0x59, 0x93, 0x18, 0x5f, 0xef, 0x9b, 0xa3, + 0x68, 0xf1, 0x60, 0xd8, 0xda, 0xf4, 0x32, 0xd0, 0x8b, 0xa9, 0xf1, 0xed, 0x1e, 0x5a, 0xbe, 0x6c, + 0xc6, 0x92, 0x91, 0xe0, 0xfa, 0x2f, 0xe0, 0x00, 0x6a, 0x52, 0x57, 0x0e, 0xf1, 0x8c, 0x19, 0xde, + 0xf4, 0xe6, 0x17, 0xc3, 0x3c, 0xe5, 0x2e, 0xf0, 0xa6, 0xe5, 0xfb, 0xe3, 0x18, 0xcb, 0x03, 0x87}, + 64}), + [](const ::testing::TestParamInfo& info) { + return info.param.GetTestName(); + }); + +class ParameterizedHashStreamingTest : public ::testing::TestWithParam +{ + protected: + void SetUp() override + { + auto conn_result = CreateConnectionWithOpen(); + ASSERT_TRUE(conn_result.has_value()) << "Failed to create connection"; + auto pair = std::move(conn_result).value(); + _connection = std::move(pair.first); + _connection_id = pair.second; + } + + std::unique_ptr _connection; + std::uint64_t _connection_id; +}; + +TEST_P(ParameterizedHashStreamingTest, StreamingHashWithDifferentAlgorithmsAndData) +{ + auto test_data = GetParam(); + + // 1. Create context with CTX_CREATE and specify algorithm + auto ctxIdRes = CreateContext(_connection.get(), _connection_id, test_data.algorithm); + ASSERT_TRUE(ctxIdRes.has_value()) << "Failed to create context"; + uint64_t context_id = ctxIdRes.value(); + std::cout << "Created context with context_id: " << context_id << " using algorithm: " << test_data.algorithm + << std::endl; + + // 2. HASH_INIT - no algorithm parameter needed anymore + auto initResponse = daemon::control_plane::protocol::ControlRequestBuilder() + .forDataNodeId(context_id) + .operation({daemon::common::actors::OP_ACTOR_HASH_HANDLER, + daemon::provider::handler::hash_handler_operations::HASH_INIT}) + .build(); + + if (!initResponse.has_value()) + { + CloseContext(_connection.get(), context_id); + ASSERT_TRUE(false) << "Failed to build HASH_INIT request"; + } + + auto initResponseRes = _connection->SendRequest(initResponse.value()); + auto initValidator = daemon::control_plane::protocol::ControlResponseValidator::FromResult(initResponseRes); + ASSERT_TRUE(initValidator.isValid()) << initValidator.getError(); + + initValidator + .expectOperation({daemon::common::actors::OP_ACTOR_HASH_HANDLER, + daemon::provider::handler::hash_handler_operations::HASH_INIT}) + .expectSuccess(); + ASSERT_TRUE(initValidator.isValid()) << initValidator.getError(); + + // 3. HASH_UPDATE (split data into two parts) + size_t split_point = test_data.input_data.size() / 2; + std::string data1_str = test_data.input_data.substr(0, split_point); + std::string data2_str = test_data.input_data.substr(split_point); + + std::vector data1(data1_str.begin(), data1_str.end()); + auto updateResponse1 = daemon::control_plane::protocol::ControlRequestBuilder() + .forDataNodeId(context_id) + .operation({daemon::common::actors::OP_ACTOR_HASH_HANDLER, + daemon::provider::handler::hash_handler_operations::HASH_UPDATE}) + .with_in_data_buffer(data1) + .build(); + + if (!updateResponse1.has_value()) + { + CloseContext(_connection.get(), context_id); + ASSERT_TRUE(false) << "Failed to build HASH_UPDATE (1) request"; + } + + auto updateResponseRes1 = _connection->SendRequest(updateResponse1.value()); + auto updateValidator1 = daemon::control_plane::protocol::ControlResponseValidator::FromResult(updateResponseRes1); + ASSERT_TRUE(updateValidator1.isValid()) << updateValidator1.getError(); + + updateValidator1 + .expectOperation({daemon::common::actors::OP_ACTOR_HASH_HANDLER, + daemon::provider::handler::hash_handler_operations::HASH_UPDATE}) + .expectSuccess(); + ASSERT_TRUE(updateValidator1.isValid()) << updateValidator1.getError(); + + // 4. HASH_UPDATE (part 2) + std::vector data2(data2_str.begin(), data2_str.end()); + auto updateResponse2 = daemon::control_plane::protocol::ControlRequestBuilder() + .forDataNodeId(context_id) + .operation({daemon::common::actors::OP_ACTOR_HASH_HANDLER, + daemon::provider::handler::hash_handler_operations::HASH_UPDATE}) + .with_in_data_buffer(data2) + .build(); + + if (!updateResponse2.has_value()) + { + CloseContext(_connection.get(), context_id); + ASSERT_TRUE(false) << "Failed to build HASH_UPDATE (2) request"; + } + + auto updateResponseRes2 = _connection->SendRequest(updateResponse2.value()); + auto updateValidator2 = daemon::control_plane::protocol::ControlResponseValidator::FromResult(updateResponseRes2); + ASSERT_TRUE(updateValidator2.isValid()) << updateValidator2.getError(); + + updateValidator2 + .expectOperation({daemon::common::actors::OP_ACTOR_HASH_HANDLER, + daemon::provider::handler::hash_handler_operations::HASH_UPDATE}) + .expectSuccess(); + ASSERT_TRUE(updateValidator2.isValid()) << updateValidator2.getError(); + + // 5. HASH_FINISH + auto finishResponse = daemon::control_plane::protocol::ControlRequestBuilder() + .forDataNodeId(context_id) + .operation({daemon::common::actors::OP_ACTOR_HASH_HANDLER, + daemon::provider::handler::hash_handler_operations::HASH_FINISH}) + .build(); + + if (!finishResponse.has_value()) + { + CloseContext(_connection.get(), context_id); + ASSERT_TRUE(false) << "Failed to build HASH_FINISH request"; + } + + auto finishResponseRes = _connection->SendRequest(finishResponse.value()); + auto finishValidator = daemon::control_plane::protocol::ControlResponseValidator::FromResult(finishResponseRes); + ASSERT_TRUE(finishValidator.isValid()) << finishValidator.getError(); + + finishValidator + .expectOperation({daemon::common::actors::OP_ACTOR_HASH_HANDLER, + daemon::provider::handler::hash_handler_operations::HASH_FINISH}) + .expectSuccess(); + + ASSERT_TRUE(finishValidator.isValid()) << finishValidator.getError(); + + // 6. Verify hash output + auto hashOutput = finishValidator.getParameterAt(0, 0); + ASSERT_TRUE(hashOutput.has_value()); + ASSERT_EQ(hashOutput.value().size(), test_data.expected_size) + << test_data.algorithm << " should produce " << test_data.expected_size << " bytes"; + + ASSERT_EQ(hashOutput.value(), test_data.expected_hash) + << "Hash output does not match expected " << test_data.algorithm << " for '" << test_data.input_data << "'"; + + // 7. Close the context + ASSERT_TRUE(CloseContext(_connection.get(), context_id)) << "Failed to close context"; + std::cout << "Closed context with context_id: " << context_id << std::endl; + + // 8. Close the connection + ASSERT_TRUE(CloseConnection(_connection.get(), _connection_id)) << "Failed to close connection"; +} + +INSTANTIATE_TEST_SUITE_P( + HashStreamingAlgorithmsAndData, + ParameterizedHashStreamingTest, + ::testing::Values( + // SHA256 test cases + HashTestData{"SHA256", + "Hello, World!", + {0xdf, 0xfd, 0x60, 0x21, 0xbb, 0x2b, 0xd5, 0xb0, 0xaf, 0x67, 0x62, 0x90, 0x80, 0x9e, 0xc3, 0xa5, + 0x31, 0x91, 0xdd, 0x81, 0xc7, 0xf7, 0x0a, 0x4b, 0x28, 0x68, 0x8a, 0x36, 0x21, 0x82, 0x98, 0x6f}, + 32}, + HashTestData{"SHA256", + "Hello S-Core", + {0xcf, 0x4a, 0x68, 0x50, 0x44, 0x51, 0x2f, 0xbc, 0xb1, 0x08, 0xeb, 0x37, 0x25, 0x48, 0x5b, 0x61, + 0x02, 0x6f, 0x7d, 0xb4, 0x2b, 0x70, 0xef, 0x78, 0xee, 0x4f, 0x96, 0x96, 0x23, 0x17, 0x45, 0x25}, + 32}, + // SHA512 test cases + HashTestData{"SHA512", + "Hello, World!", + {0x37, 0x4d, 0x79, 0x4a, 0x95, 0xcd, 0xcf, 0xd8, 0xb3, 0x59, 0x93, 0x18, 0x5f, 0xef, 0x9b, 0xa3, + 0x68, 0xf1, 0x60, 0xd8, 0xda, 0xf4, 0x32, 0xd0, 0x8b, 0xa9, 0xf1, 0xed, 0x1e, 0x5a, 0xbe, 0x6c, + 0xc6, 0x92, 0x91, 0xe0, 0xfa, 0x2f, 0xe0, 0x00, 0x6a, 0x52, 0x57, 0x0e, 0xf1, 0x8c, 0x19, 0xde, + 0xf4, 0xe6, 0x17, 0xc3, 0x3c, 0xe5, 0x2e, 0xf0, 0xa6, 0xe5, 0xfb, 0xe3, 0x18, 0xcb, 0x03, 0x87}, + 64}), + [](const ::testing::TestParamInfo& info) { + return info.param.GetTestName(); + }); + +class ParallelRequests : public ::testing::TestWithParam +{ + protected: + void SetUp() override + { + auto conn_result = CreateConnectionWithOpen(); + ASSERT_TRUE(conn_result.has_value()) << "Failed to create connection"; + auto pair = std::move(conn_result).value(); + _connection = std::move(pair.first); + _connection_id = pair.second; + } + + std::unique_ptr _connection; + std::uint64_t _connection_id; +}; + +TEST_P(ParallelRequests, ParallelDummyRequests) +{ + const int num_threads = GetParam(); + std::vector threads; + threads.reserve(num_threads); + + Barrier sync_barrier(num_threads); + // std::vector is not sufficient here. + // It does not guarantee proper concurrent access to individual elements from different threads. + std::vector success_flags(num_threads, 0); + + for (int i = 0; i < num_threads; ++i) + { + threads.emplace_back([i, &sync_barrier, &success_flags]() { + // Create a connection for this thread + auto conn_result = CreateConnectionWithOpen(); + + if (!conn_result.has_value()) + { + success_flags[i] = 0; + return; + } + auto pair = std::move(conn_result).value(); + auto connection = std::move(pair.first); + auto connection_id = pair.second; + + // Wait for all threads to be ready + sync_barrier.Wait(); + + // Send request + daemon::control_plane::protocol::ControlRequest request; + request.data_node_id = connection_id; + + // Verify response + auto response = connection->SendRequest(request); + success_flags[i] = response.has_value() ? 1 : 0; + }); + } + + // Wait for all threads to complete + for (auto& thread : threads) + { + thread.join(); + } + + // Verify all requests succeeded + for (int i = 0; i < num_threads; ++i) + { + EXPECT_NE(success_flags[i], 0) << "Thread " << i << " failed"; + } +} + +INSTANTIATE_TEST_SUITE_P(ThreadCounts, ParallelRequests, ::testing::Values(16)); + +} // namespace score::crypto::test + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/integration_tests/init_softhsm_token.cpp b/tests/integration_tests/init_softhsm_token.cpp new file mode 100644 index 0000000..e61b1a5 --- /dev/null +++ b/tests/integration_tests/init_softhsm_token.cpp @@ -0,0 +1,396 @@ +// ******************************************************************************* +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +/// @brief Standalone helper binary that initialises a SoftHSM2 token via the raw +/// PKCS#11 C API. +/// +/// Usage: +/// init_softhsm_token --token-dir --config-path +/// --token-label