diff --git a/.bazelrc b/.bazelrc index 51cac5a..540ce84 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,9 +1,107 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* + +# Import user-specific configurations +try-import %workspace%/user.bazelrc + +build --per_file_copt=external/score_baselibs.*/.*@-Wno-deprecated-declarations +common --@score_baselibs//score/json:base_library=nlohmann # TODO : https://github.com/eclipse-score/baselibs/issues/57 + 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/.clang-format b/.clang-format new file mode 100644 index 0000000..567fdc0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,47 @@ +BasedOnStyle: Google +AccessModifierOffset: -2 +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +# Empty is required in AllowShortFunctionsOnASingleLine over Inline because Inline contradicts with AUTOSAR rule A7-1-7 +# Such rule is no longer existing in MISRA C++:2023, once we are fully migrated to MISRA C++:2023, switching to Inline +# could be reconsidered +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false +BreakBeforeBraces: Custom +ColumnLimit: 120 +DerivePointerAlignment: false +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^(<|")(assert|complex|ctype|errno|fenv|float|inttypes|iso646|limits|locale|math|setjmp|signal|stdalign|stdargh|stdatomic|stdbool|stddef|stdint|stdio|stdlib|stdnoreturn|string|tgmath|threads|time|uchar|wchar|wctype)\.h(>|")$' + Priority: 2 + - Regex: '^(<|")(cstdlib|csignal|csetjmp|cstdarg|typeinfo|typeindex|type_traits|bitset|functional|utility|ctime|chrono|cstddef|initializer_list|tuple|any|optional|variant|new|memory|scoped_allocator|memory_resource|climits|cfloat|cstdint|cinttypes|limits|exception|stdexcept|cassert|system_error|cerrno|cctype|cwctype|cstring|cwchar|cuchar|string|string_view|array|vector|deque|list|forward_list|set|map|unordered_set|unordered_map|stack|queue|algorithm|execution|teratorslibrary|iterator|cmath|complex|valarray|random|numeric|ratio|cfenv|iosfwd|ios|istream|ostream|iostream|fstream|sstream|strstream|iomanip|streambuf|cstdio|locale|clocale|codecvt|regex|atomic|thread|mutex|shared_mutex|future|condition_variable|filesystem|ciso646|ccomplex|ctgmath|cstdalign|cstdbool)(>|")$' + Priority: 3 + - Regex: '^(<|").*(>|")$' + Priority: 1 +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: true +QualifierAlignment: Left +CommentPragmas: '^.*A2Lfactory:' +--- +# Make sure language specific settings are below the generic settings to be compatible to all languages. +Language: Cpp +Standard: c++17 diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..dfd238f --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,16 @@ +# NOTE: the set of enabled checks is yet subject to be tailored for the S-CORE project +Checks: > + -*, clang-analyzer-*, cert-*, cppcoreguidelines-*, bugprone-*, misc-*, performance-*, readability-* +# Only treat critical checks as errors, others are warnings +# NOTE: the `WarningsAsErrors` property is yet subject to be tailored for the enabled checks +WarningsAsErrors: > + clang-analyzer-*, +# Exclude third-party and external dependencies' headers from analysis +HeaderFilterRegex: '^(?!.*/third_party/).*' +FormatStyle: file +# The rules above are permissive and we need the rules below to enforce naming conventions +CheckOptions: + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.StructCase + value: CamelCase diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..ea48ce5 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,32 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* + +# Use Dockerfile to get dependabot version bumps after new image is released +FROM ghcr.io/eclipse-score/devcontainer:v1.4.1 + +# Build argument for dynamic username (defaults to 'vscode' if not provided) +ARG USERNAME=vscode + +# RENAME 'vscode' user to the specified username to satisfy the QNX license +# We use usermod to change the login name and home directory +RUN if [ "$USERNAME" != "vscode" ]; then \ + usermod -l ${USERNAME} vscode \ + && groupmod -n ${USERNAME} vscode \ + && usermod -d /home/${USERNAME} -m ${USERNAME} \ + && ln -s /home/${USERNAME} /home/vscode \ + && echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/${USERNAME} \ + && chmod 0440 /etc/sudoers.d/${USERNAME}; \ + fi + +# Set the default user for the container +USER ${USERNAME} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..a1981f3 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,47 @@ +{ + "name": "eclipse-s-core", + "build": { + "dockerfile": "Dockerfile", + "args": { + "USERNAME": "${localEnv:USER}" + } + }, + "remoteUser": "${localEnv:USER}", + + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {} + }, + + // Create empty folder in case mandatory mount points are missing. + "initializeCommand": "mkdir -p ${localEnv:HOME}/.cache/bazel /usr/local/share/ca-certificates/ ${localEnv:HOME}/.qnx/license && sudo mkdir -p /etc/docker", + + "mounts": [ + { + "source": "${localEnv:HOME}/.cache/bazel", + "target": "/home/${localEnv:USER}/.cache/bazel", + "type": "bind" + }, + { + "source": "${localEnv:HOME}/.qnx/license", + "target": "/opt/score_qnx/license", + "type": "bind" + }, + { + "source": "/usr/local/share/ca-certificates/", + "target": "/usr/local/share/ca-certificates/", + "type": "bind" + }, + { + "source": "/etc/docker", + "target": "/etc/docker", + "type": "bind" + } + + ], + "onCreateCommand": { + "update certificates & install acl library": "sudo apt update && sudo apt install -y --no-install-recommends ca-certificates-java openjdk-17-jre-headless && 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" + }, + + "runArgs": ["--network", "host"] +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ff063c4..f066ddd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,16 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* + # Comment out as not in score yet # * @eclipse-score/infrastructure-tooling-community # .* @eclipse-score/infrastructure-tooling-community @@ -6,10 +19,10 @@ # in separate repositories: # # /docs @eclipse-score/process-community -# /docs/manual @eclipse-score//safety-manager +# /docs/manuals @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 +# /docs/verification_report @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 index 8341f51..fb54341 100644 --- a/.github/PULL_REQUEST_TEMPLATE/bug_fix.md +++ b/.github/PULL_REQUEST_TEMPLATE/bug_fix.md @@ -1,3 +1,18 @@ + + # Bugfix > [!IMPORTANT] diff --git a/.github/PULL_REQUEST_TEMPLATE/improvement.md b/.github/PULL_REQUEST_TEMPLATE/improvement.md index 090ad43..334ce94 100644 --- a/.github/PULL_REQUEST_TEMPLATE/improvement.md +++ b/.github/PULL_REQUEST_TEMPLATE/improvement.md @@ -1,3 +1,18 @@ + + # Improvement > [!IMPORTANT] diff --git a/.github/actions/gitlint/action.yml b/.github/actions/gitlint/action.yml index cb16e6f..8403839 100644 --- a/.github/actions/gitlint/action.yml +++ b/.github/actions/gitlint/action.yml @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2024 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..aa645e7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,74 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily + groups: + all-in-one: + patterns: + - "*" + update-types: + - "minor" + - "patch" + + - package-ecosystem: cargo + directory: / + schedule: + interval: daily + groups: + all-in-one: + patterns: + - "*" + update-types: + - "minor" + - "patch" + + - package-ecosystem: pip + directory: / + schedule: + interval: daily + groups: + all-in-one: + patterns: + - "*" + update-types: + - "minor" + - "patch" + + - package-ecosystem: docker + directories: + - /.devcontainer + schedule: + interval: daily + groups: + all-in-one: + patterns: + - "*" + update-types: + - "minor" + - "patch" + + - package-ecosystem: bazel + directory: / + schedule: + interval: daily + groups: + all-in-one: + patterns: + - "*" + update-types: + - "minor" + - "patch" diff --git a/.github/templatesyncignore.txt b/.github/templatesyncignore.txt new file mode 100644 index 0000000..bf8a22f --- /dev/null +++ b/.github/templatesyncignore.txt @@ -0,0 +1,17 @@ +**/Module.bazel +.DS_Store +.bazelversion +.gitignore +# TODO: Remove once we have harmonized the pre-commit workflow +.pre-commit-config.yaml +**/BUILD +platforms/ +score/ +src/ +tests/ +third_party/ +examples/ +docs/ +README.md +project_config.bzl +CODEOWNERS diff --git a/.github/workflows/bzlmod-lock.yml b/.github/workflows/bzlmod-lock.yml new file mode 100644 index 0000000..f84f7d7 --- /dev/null +++ b/.github/workflows/bzlmod-lock.yml @@ -0,0 +1,28 @@ +# ******************************************************************************* +# Copyright (c) 2026 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: Bzlmod Lockfile Check +permissions: + contents: read +on: + pull_request: + types: [opened, reopened, synchronize] + merge_group: + types: [checks_requested] + push: + branches: + - main +jobs: + bzlmod-lock: + uses: eclipse-score/cicd-workflows/.github/workflows/bzlmod-lock-check.yml@829b3e11ccbf924a5782f7bfed647cb1619fdf78 # v0.0.1 + with: + working-directory: . diff --git a/.github/workflows/copyright.yml b/.github/workflows/copyright.yml index 08ef376..b0b65fc 100644 --- a/.github/workflows/copyright.yml +++ b/.github/workflows/copyright.yml @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2024 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -19,6 +19,6 @@ on: types: [checks_requested] jobs: copyright-check: - uses: eclipse-score/cicd-workflows/.github/workflows/copyright.yml@main + uses: eclipse-score/cicd-workflows/.github/workflows/copyright.yml@829b3e11ccbf924a5782f7bfed647cb1619fdf78 # v0.0.1 with: - bazel-target: "run //:copyright.check" + bazel-target: "run --lockfile_mode=error //:copyright.check" diff --git a/.github/workflows/docs-cleanup.yml b/.github/workflows/docs-cleanup.yml index cfa4ae2..7941eca 100644 --- a/.github/workflows/docs-cleanup.yml +++ b/.github/workflows/docs-cleanup.yml @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -24,6 +24,6 @@ on: jobs: docs-cleanup: - uses: eclipse-score/cicd-workflows/.github/workflows/docs-cleanup.yml@main + uses: eclipse-score/cicd-workflows/.github/workflows/docs-cleanup.yml@829b3e11ccbf924a5782f7bfed647cb1619fdf78 # v0.0.1 secrets: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 24bd399..bab7cec 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -30,7 +30,7 @@ on: jobs: build-docs: - uses: eclipse-score/cicd-workflows/.github/workflows/docs.yml@main + uses: eclipse-score/cicd-workflows/.github/workflows/docs.yml@829b3e11ccbf924a5782f7bfed647cb1619fdf78 # v0.0.1 permissions: contents: write pages: write @@ -39,5 +39,5 @@ jobs: 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 }}" + bazel-target: "--lockfile_mode=error //:docs -- --github_user=${{ github.repository_owner }} --github_repo=${{ github.event.repository.name }}" retention-days: 3 diff --git a/.github/workflows/gitlint.yml b/.github/workflows/gitlint.yml index 90487ed..1403f0c 100644 --- a/.github/workflows/gitlint.yml +++ b/.github/workflows/gitlint.yml @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2024 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. diff --git a/.github/workflows/license_check.yml b/.github/workflows/license_check.yml index aba7f99..2cb2caf 100644 --- a/.github/workflows/license_check.yml +++ b/.github/workflows/license_check.yml @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -25,8 +25,9 @@ permissions: jobs: license-check: - uses: eclipse-score/cicd-workflows/.github/workflows/license-check.yml@main + uses: eclipse-score/cicd-workflows/.github/workflows/license-check.yml@829b3e11ccbf924a5782f7bfed647cb1619fdf78 # v0.0.1 with: repo-url: "${{ github.server_url }}/${{ github.repository }}" + bazel-target: "run --lockfile_mode=error //:license-check" secrets: dash-api-token: ${{ secrets.ECLIPSE_GITLAB_API_TOKEN }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..f90eb9c --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,32 @@ +# ******************************************************************************* +# Copyright (c) 2026 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: pre-commit +on: + pull_request: + types: [opened, reopened, synchronize] +jobs: + self_test: + name: 🔬 Self Test + runs-on: ubuntu-latest + steps: + - name: 📥 Check out + uses: actions/checkout@v6 + - name: 🛠️ Install pre-commit + run: pip install pre-commit + # TODO: Harmonize pre-commit configuration with module_template + # TODO: For now, our clang-tidy execution is not clean + # Skip pre-commit checks, reactivate after fixing all clang-tidy issues + # Hint: If this reactivated and does also cover formatting checks, remove the separate workflow + # Hint: Also activate sync again in .github/templatesyncignore.txt + # - name: ✅ Run pre-commit + # run: pre-commit run --all-files diff --git a/.github/workflows/sync-template.yml b/.github/workflows/sync-template.yml new file mode 100644 index 0000000..a9efa3d --- /dev/null +++ b/.github/workflows/sync-template.yml @@ -0,0 +1,44 @@ +# ******************************************************************************* +# Copyright (c) 2026 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: Sync with template +on: + schedule: + - cron: "0 0 1 * *" + workflow_dispatch: + +jobs: + repo-sync: + if: github.repository != 'eclipse-score/module_template' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: false # needed see #557 and #627 + - name: Sync Template Changes + uses: AndreasAugustin/actions-template-sync@v2.5.3 + with: + source_gh_token: ${{ secrets.SCORE_APPROVALS_PAT }} + target_gh_token: ${{ secrets.SCORE_APPROVALS_PAT}} + source_repo_path: "eclipse-score/module_template" + upstream_branch: "main" + pr_title: "[Template Sync] Upstream template update" + pr_commit_msg: "chore(template): upstream template update" + git_user_name: eclipse-score-bot + git_user_email: 187756813+eclipse-score-bot@users.noreply.github.com + template_sync_ignore_file_path: ".github/.templatesyncignore" diff --git a/.gitignore b/.gitignore index e7dc329..b691756 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,16 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* + # Prerequisites *.d @@ -34,7 +47,6 @@ # Bazel bazel-* -MODULE.bazel.lock user.bazelrc # Ruff @@ -51,6 +63,12 @@ styles/ .envrc # Python -.venv +.venv* __pycache__/ /.coverage +.python-version +path/to/venv/ + +# Language Server +compile_commands.json +.cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5d16ed1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,41 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* +repos: + - repo: local + hooks: + - id: copyright-fix + name: //:copyright-fix + entry: bazel run //:copyright-fix + language: system + pass_filenames: false + - id: format-fix + name: //:format.fix + entry: bazel run //:format.fix + language: system + files: (BUILD|BUILD\.bazel|WORKSPACE|\.bazelrc|[^/]+\.(py|bzl|bazel|yaml|yml|rs))$ + pass_filenames: false + - id: generate-compile-commands + name: bazel-compile-commands (for clang-tidy) + entry: bazel-compile-commands + language: system + files: \.(c|cpp|h|hpp)$ + pass_filenames: false + - repo: https://github.com/pocc/pre-commit-hooks + rev: v1.3.5 + hooks: + - id: clang-format + args: ["-i", "--style=file"] + files: \.(c|cpp|h|hpp)$ + - id: clang-tidy + args: ["--config-file=.clang-tidy", "-p=$(pwd)", "--warnings-as-errors=*"] + exclude: '_test\.cpp$' diff --git a/.yamlfmt b/.yamlfmt index 2078769..9bab1d2 100644 --- a/.yamlfmt +++ b/.yamlfmt @@ -1,3 +1,16 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* + formatter: type: basic retain_line_breaks: true diff --git a/BUILD b/BUILD index 473b5d5..c32ec5e 100644 --- a/BUILD +++ b/BUILD @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -12,19 +12,19 @@ # ******************************************************************************* 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") +load("@score_tooling//:defs.bzl", "copyright_checker", "use_format_targets") -setup_starpls( - name = "starpls_server", - visibility = ["//visibility:public"], +docs( + source_dir = "docs", ) copyright_checker( name = "copyright", srcs = [ - "src", - "tests", + # ".github", + "docs", + "score", + "third_party", "//:BUILD", "//:MODULE.bazel", ], @@ -33,16 +33,4 @@ copyright_checker( 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"], -) - -# Add target for formatting checks use_format_targets() - -docs( - source_dir = "docs", -) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index dcc54e6..cfbec36 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -1,3 +1,18 @@ + + # 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. diff --git a/MODULE.bazel b/MODULE.bazel index befae8a..d513804 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -11,56 +11,225 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* 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") +single_version_override( + module_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") + +# 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") -# Rust rules for Bazel -bazel_dep(name = "rules_rust", version = "0.63.0") +# =============================================================================== +# T O O L C H A I N S +# =============================================================================== -# C/C++ rules for Bazel -bazel_dep(name = "rules_cc", version = "0.2.14") +# ******************************************************************************* +# 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", +) + +# ******************************************************************************* +# 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", ) -use_repo(llvm, "llvm_toolchain") -use_repo(llvm, "llvm_toolchain_llvm") -register_toolchains("@llvm_toolchain//:all") +# =============================================================================== +# 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.1.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.2.7") + +# Integration testing +bazel_dep(name = "score_itf", version = "0.1.0") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 0000000..ed06e64 --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,1614 @@ +{ + "lockFileVersion": 18, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/ape/1.0.1/MODULE.bazel": "37411cfd13bfc28cd264674d660a3ecb3b5b35b9dbe4c0b2be098683641b3fee", + "https://bcr.bazel.build/modules/ape/1.0.1/source.json": "96bc5909d1e3ccc4203272815ef874dbfd99651e240c05049f12193d16c1110b", + "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", + "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", + "https://bcr.bazel.build/modules/apple_support/1.17.1/MODULE.bazel": "655c922ab1209978a94ef6ca7d9d43e940cd97d9c172fb55f94d91ac53f8610b", + "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", + "https://bcr.bazel.build/modules/apple_support/1.22.1/MODULE.bazel": "90bd1a660590f3ceffbdf524e37483094b29352d85317060b2327fff8f3f4458", + "https://bcr.bazel.build/modules/apple_support/1.22.1/source.json": "2bc34da8d0ebc4c4132c8b26db766ca1b86bbcf26dea94b94aa1cd73e2623aeb", + "https://bcr.bazel.build/modules/aspect_bazel_lib/1.31.2/MODULE.bazel": "7bee702b4862612f29333590f4b658a5832d433d6f8e4395f090e8f4e85d442f", + "https://bcr.bazel.build/modules/aspect_bazel_lib/1.38.0/MODULE.bazel": "6307fec451ba9962c1c969eb516ebfe1e46528f7fa92e1c9ac8646bef4cdaa3f", + "https://bcr.bazel.build/modules/aspect_bazel_lib/1.42.2/MODULE.bazel": "2e0d8ab25c57a14f56ace1c8e881b69050417ff91b2fb7718dc00d201f3c3478", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.0.0/MODULE.bazel": "e118477db5c49419a88d78ebc7a2c2cea9d49600fe0f490c1903324a2c16ecd9", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.11.0/MODULE.bazel": "cb1ba9f9999ed0bc08600c221f532c1ddd8d217686b32ba7d45b0713b5131452", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.16.0/MODULE.bazel": "852f9ebbda017572a7c113a2434592dd3b2f55cd9a0faea3d4be5a09a59e4900", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.20.0/MODULE.bazel": "c5565bac49e1973227225b441fad1c938d498d83df62dc5da95b2fab0f0626a2", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.20.0/source.json": "3eaada79dd3c65b6c57d5fc33c57ffd2896c4ebd78c4c9001a790a70f7f50e61", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.9.3/MODULE.bazel": "66baf724dbae7aff4787bf2245cc188d50cb08e07789769730151c0943587c14", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.9.4/MODULE.bazel": "ccc41028429f894b02fde7ef67d416cba3ba5084ed9ddb9bb6107aa82d118776", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.21.0/MODULE.bazel": "77dc393c43ad79398b05865444c5200c6f1aae6765615544f2c7730b5858d533", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.21.0/source.json": "062b1d3dba8adcfeb28fe60c185647f5a53ec0487ffe93cf0ae91566596e4b49", + "https://bcr.bazel.build/modules/aspect_rules_js/1.33.1/MODULE.bazel": "db3e7f16e471cf6827059d03af7c21859e7a0d2bc65429a3a11f005d46fc501b", + "https://bcr.bazel.build/modules/aspect_rules_js/1.40.0/MODULE.bazel": "01a1014e95e6816b68ecee2584ae929c7d6a1b72e4333ab1ff2d2c6c30babdf1", + "https://bcr.bazel.build/modules/aspect_rules_js/2.0.0/MODULE.bazel": "b45b507574aa60a92796e3e13c195cd5744b3b8aff516a9c0cb5ae6a048161c5", + "https://bcr.bazel.build/modules/aspect_rules_js/2.1.3/MODULE.bazel": "47cc48eec374d69dced3cf9b9e5926beac2f927441acfb1a3568bbb709b25666", + "https://bcr.bazel.build/modules/aspect_rules_js/2.3.8/MODULE.bazel": "74bf20a7a6bd5f2be09607fdb4196cfd6f203422ea271752ec2b1afe95426101", + "https://bcr.bazel.build/modules/aspect_rules_js/2.3.8/source.json": "411ec9d79d6f5fe8a083359588c21d01a5b48d88a2cbd334a4c90365015b7836", + "https://bcr.bazel.build/modules/aspect_rules_lint/0.12.0/MODULE.bazel": "e767c5dbfeb254ec03275a7701b5cfde2c4d2873676804bc7cb27ddff3728fed", + "https://bcr.bazel.build/modules/aspect_rules_lint/1.0.0-rc9/MODULE.bazel": "7c0d5173bf7c3430fb99213c2664fb7fd0a4530e37de9a7c6006275e73036001", + "https://bcr.bazel.build/modules/aspect_rules_lint/1.0.3/MODULE.bazel": "ed0fe929647ba21d2041e14ea3d757133ca306b72d4998e8a3d0d2f515196765", + "https://bcr.bazel.build/modules/aspect_rules_lint/1.3.1/MODULE.bazel": "06ce330900a7d6403bc8d88e5dfad6aeeb8ae40179f66bb89e69c8bf6f6b1a0b", + "https://bcr.bazel.build/modules/aspect_rules_lint/1.4.2/MODULE.bazel": "78d025facf6fa675fd6f0b62fd6a9a2bec7ef5ae1e288e5b53f4383b98017105", + "https://bcr.bazel.build/modules/aspect_rules_lint/1.4.4/MODULE.bazel": "24459eeeeb084bc3e7628c338e494746718bc17b3a3cbd94415c8df5c7c6dc37", + "https://bcr.bazel.build/modules/aspect_rules_lint/1.5.3/MODULE.bazel": "7fee71b11be63f1cf0458cd8c731712a0e672d0bb6df8879ed70249bf8dfdfdc", + "https://bcr.bazel.build/modules/aspect_rules_lint/1.5.3/source.json": "b05f2377bb1239cd92053bb45d5e9fe8570e68d6bc5775099cf6d572312caaf2", + "https://bcr.bazel.build/modules/aspect_rules_py/1.0.0/MODULE.bazel": "8eb29876512d3242af50a424300bec5c5f8957b455963df5f618cb7fd4e8ae19", + "https://bcr.bazel.build/modules/aspect_rules_py/1.4.0/MODULE.bazel": "6fd29b93207a31445d5d3ab9d9882fd5511e43c95e8e82e7492872663720fd44", + "https://bcr.bazel.build/modules/aspect_rules_py/1.4.0/source.json": "fb1ba946478fb6dbb26d49307d756b0fd2ff88be339af23c39c0397d59143d2c", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.4.0/MODULE.bazel": "5b554d5de90d96ee14117527c0519037713dd33884f3212eae391beccb2e94ff", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.6.0/MODULE.bazel": "d0045b5eabb012be550a609589b3e5e47eba682344b19cfd9365d4d896ed07df", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.6.0/source.json": "5593e3f1cd0dd5147f7748e163307fd5c2e1077913d6945b58739ad8d770a290", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.5/MODULE.bazel": "5a3c8013c3ba9ebc0a65efda40e4376b869e1260873c98020504feed55244ce8", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.5/source.json": "f64bc2cad76cda1dff0a4f024605106743bd3fef46263fcaebb706a1aae72660", + "https://bcr.bazel.build/modules/bazel_features/0.1.0/MODULE.bazel": "47011d645b0f949f42ee67f2e8775188a9cf4a0a1528aa2fa4952f2fd00906fd", + "https://bcr.bazel.build/modules/bazel_features/1.0.0/MODULE.bazel": "d7f022dc887efb96e1ee51cec7b2e48d41e36ff59a6e4f216c40e4029e1585bf", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", + "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.13.0/MODULE.bazel": "c14c33c7c3c730612bdbe14ebbb5e61936b6f11322ea95a6e91cd1ba962f94df", + "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", + "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.2.0/MODULE.bazel": "122b2b606622afbaa498913d54f52d9bcd2d19a5edd1bd6d6c5aa17441c4d5f9", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", + "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a", + "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", + "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", + "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", + "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", + "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/MODULE.bazel": "72997b29dfd95c3fa0d0c48322d05590418edef451f8db8db5509c57875fb4b7", + "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/source.json": "7ad77c1e8c1b84222d9b3f3cae016a76639435744c19330b0b37c0a3c9da7dc0", + "https://bcr.bazel.build/modules/boost.assert/1.83.0.bcr.4/MODULE.bazel": "0cc01302bf8f0098013098098f441b06068e6f4d05a4bb4f75a2133d11239e5b", + "https://bcr.bazel.build/modules/boost.assert/1.83.0.bcr.4/source.json": "225cb551191ac7f5fb4eba0476a0d0397405cbb360dd9755bf29b28033415379", + "https://bcr.bazel.build/modules/boost.config/1.83.0.bcr.4/MODULE.bazel": "d6433498d31931ef3ec5e237f9a780bd8dbb110a73cccaac15f99df2ff0795a9", + "https://bcr.bazel.build/modules/boost.config/1.83.0.bcr.4/source.json": "0a5b5bf8d2afcf0731fcd452fe69f20d45b0d08ba07e70698cc7ee36f0ce7e9c", + "https://bcr.bazel.build/modules/boost.container/1.83.0.bcr.4/MODULE.bazel": "8c2711b962cabb11a91dfa24c8019141d7709e2a7d9ad888c7b7bd1f702565ae", + "https://bcr.bazel.build/modules/boost.container/1.83.0.bcr.4/source.json": "246ae4564faf1c08995ac3f6a6886999e197821d0e3b3dd641911ae417657602", + "https://bcr.bazel.build/modules/boost.container_hash/1.83.0.bcr.4/MODULE.bazel": "2f8246d0e34f19d3e09ba41022e85775566449f6a133640e65b4d9f1cbf110b8", + "https://bcr.bazel.build/modules/boost.container_hash/1.83.0.bcr.4/source.json": "0410e34fef0a3dc16edc0f6a8f49783cba04a891183d76286ba974e4d9806ef0", + "https://bcr.bazel.build/modules/boost.core/1.83.0.bcr.4/MODULE.bazel": "145932521c0509dbb268a8f062ba5432f0fe2620e6f82a580dab0eacda79a140", + "https://bcr.bazel.build/modules/boost.core/1.83.0.bcr.4/source.json": "d709cff67716e366200ca77680b39e9c98141c7289ddbf207b1c710f4de7bf03", + "https://bcr.bazel.build/modules/boost.describe/1.83.0.bcr.4/MODULE.bazel": "e705d8ed1d12aba965bb860b9130d05738854168b28573283a75dfa9d98ebc86", + "https://bcr.bazel.build/modules/boost.describe/1.83.0.bcr.4/source.json": "748e4e74723c381d304f888849efd52caf5710f48ceb67b91d4e43fc800ccd81", + "https://bcr.bazel.build/modules/boost.interprocess/1.83.0.bcr.4/MODULE.bazel": "b1c4cf58b387c07c10cea0709ad808283c62aa8024bebc9ce7a59248c9980729", + "https://bcr.bazel.build/modules/boost.interprocess/1.83.0.bcr.4/source.json": "4f9c40d790c736a619c03b5788b80575fadffbb712fb10944572b420313c5dee", + "https://bcr.bazel.build/modules/boost.intrusive/1.83.0.bcr.4/MODULE.bazel": "8cfe7ec4ae0e4c33297df4edf5c22e6498cde0d7a1ecf98720392b829918eb61", + "https://bcr.bazel.build/modules/boost.intrusive/1.83.0.bcr.4/source.json": "04475d40cac42ebe96888a0599b2b9b35d4092502710f421cb3dc9ad2c6b8099", + "https://bcr.bazel.build/modules/boost.move/1.83.0.bcr.4/MODULE.bazel": "ddc12982f87e67597eeede240369cc73d4ba83ec764a752c4a471245834ee060", + "https://bcr.bazel.build/modules/boost.move/1.83.0.bcr.4/source.json": "6f9971fd617c8d94e7c4a02c618052edec5ad0f30a1b18b4bf9e995443545330", + "https://bcr.bazel.build/modules/boost.mp11/1.83.0.bcr.4/MODULE.bazel": "27fa864c5e1799e514bbe2f0b9439ad29380f5d436ae3b39c29109609d81316e", + "https://bcr.bazel.build/modules/boost.mp11/1.83.0.bcr.4/source.json": "8c2924bab093f7d0e44cb2ac55dea985497b9d85d6a246c89377a49d27d452a6", + "https://bcr.bazel.build/modules/boost.predef/1.83.0.bcr.4/MODULE.bazel": "4d842ef8cadb835dcfff0ee90a7ff0b6e8df4774c72e4e8443ca2339083cd58b", + "https://bcr.bazel.build/modules/boost.predef/1.83.0.bcr.4/source.json": "4bc4c6af4bcc909b1d4abebba5583d03d5e09447c658fa63fe8fdd8a5b2906e5", + "https://bcr.bazel.build/modules/boost.preprocessor/1.83.0.bcr.4/MODULE.bazel": "aee3f5151c6c5738a494c7a2b842d1f80db0e60601cc6c1ef2b74e0c7e548fa6", + "https://bcr.bazel.build/modules/boost.preprocessor/1.83.0.bcr.4/source.json": "ce770e480d05b987d953d7e64f80cebdfb5b008a9b9da80355d9e86a3d09bbb7", + "https://bcr.bazel.build/modules/boost.static_assert/1.83.0.bcr.4/MODULE.bazel": "55a9d94e82ebdc01857bae5590e83a7afab56fc57e86f345288601992eb5cd8e", + "https://bcr.bazel.build/modules/boost.static_assert/1.83.0.bcr.4/source.json": "d14305e5679f008a352f75e8eaa725ab9ff71ec1dd2553cda1b171e39fd20cfe", + "https://bcr.bazel.build/modules/boost.throw_exception/1.83.0.bcr.4/MODULE.bazel": "6dc898672c5de822af4b4da0fc6d0ed233d9fa942356c4e1bbcd748916ba0c58", + "https://bcr.bazel.build/modules/boost.throw_exception/1.83.0.bcr.4/source.json": "ec9d540d7ca6beaffdc7f0395cd5eea0acda924b3ab306966786b242df74b512", + "https://bcr.bazel.build/modules/boost.tuple/1.83.0.bcr.4/MODULE.bazel": "3a21cf25cd86a03bdc78684f16d5b25e91f075ff95230b849545484a7d4d506b", + "https://bcr.bazel.build/modules/boost.tuple/1.83.0.bcr.4/source.json": "e145722cc962e7f4385f554ce6e8f4373bab792691712788fa16befa9082b938", + "https://bcr.bazel.build/modules/boost.type_traits/1.83.0.bcr.4/MODULE.bazel": "ecf03c91b87bc800d9c699718fab225973063fc1975082e155298e5a87e19573", + "https://bcr.bazel.build/modules/boost.type_traits/1.83.0.bcr.4/source.json": "107e5bef1443ee75efc197198b45fb01d6460b475e20d1fa0d52af64600c8204", + "https://bcr.bazel.build/modules/boost.unordered/1.83.0.bcr.4/MODULE.bazel": "122f475aca3eb7af7b3e66c1bc6a0139c95ee3629c3625ec7af7ac646be7fad6", + "https://bcr.bazel.build/modules/boost.unordered/1.83.0.bcr.4/source.json": "1afd0e208be668faeea97b63d18ef23232c522e300b454d4a2f164c369dcfb2e", + "https://bcr.bazel.build/modules/boost.winapi/1.83.0.bcr.4/MODULE.bazel": "3a36763aafd5fef4844b012bacdc104674cc3ee6a1e8b501a6d68e964a7df20e", + "https://bcr.bazel.build/modules/boost.winapi/1.83.0.bcr.4/source.json": "d4be5cdf5cfdeae83f70211faa034bc41168c75fd98a560464131c8cfc6365fa", + "https://bcr.bazel.build/modules/boost/1.83.0.bcr.4/MODULE.bazel": "0ce94f87f4514f6abd77d7ea9d221cc3432e48957c82d712829b36d53183dd68", + "https://bcr.bazel.build/modules/boost/1.83.0.bcr.4/source.json": "1c17438086d138fbaa8fbd8774b4cdf932314bdff85901c2626a94469bd7fa4f", + "https://bcr.bazel.build/modules/boringssl/0.0.0-20230215-5c22014/MODULE.bazel": "4b03dc0d04375fa0271174badcd202ed249870c8e895b26664fd7298abea7282", + "https://bcr.bazel.build/modules/boringssl/0.0.0-20240530-2db0eb3/MODULE.bazel": "d0405b762c5e87cd445b7015f2b8da5400ef9a8dbca0bfefa6c1cea79d528a97", + "https://bcr.bazel.build/modules/boringssl/0.20240913.0/MODULE.bazel": "fcaa7503a5213290831a91ed1eb538551cf11ac0bc3a6ad92d0fef92c5bd25fb", + "https://bcr.bazel.build/modules/boringssl/0.20241024.0/MODULE.bazel": "b540cff73d948cb79cb0bc108d7cef391d2098a25adabfda5043e4ef548dbc87", + "https://bcr.bazel.build/modules/boringssl/0.20241024.0/source.json": "d843092e682b84188c043ac742965d7f96e04c846c7e338187e03238674909a9", + "https://bcr.bazel.build/modules/buildifier_prebuilt/6.1.2/MODULE.bazel": "2ef4962c8b0b6d8d21928a89190755619254459bc67f870dc0ccb9ba9952d444", + "https://bcr.bazel.build/modules/buildifier_prebuilt/6.4.0/MODULE.bazel": "37389c6b5a40c59410b4226d3bb54b08637f393d66e2fa57925c6fcf68e64bf4", + "https://bcr.bazel.build/modules/buildifier_prebuilt/7.3.1/MODULE.bazel": "537faf0ad9f5892910074b8e43b4c91c96f1d5d86b6ed04bdbe40cf68aa48b68", + "https://bcr.bazel.build/modules/buildifier_prebuilt/8.2.0.2/MODULE.bazel": "a9b689711d5b69f9db741649b218c119b9fdf82924ba390415037e09798edd03", + "https://bcr.bazel.build/modules/buildifier_prebuilt/8.2.0.2/source.json": "51eb0a4b38aaaeab7fa64361576d616c4d8bfd0f17a0a10184aeab7084d79f8e", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/c-ares/1.19.1/MODULE.bazel": "73bca21720772370ff91cc8e88bbbaf14897720c6473e87c1ddc0f848284c313", + "https://bcr.bazel.build/modules/c-ares/1.19.1/source.json": "56bfa95b01e4e0012e90eaa9f1bab823c48c3af4454286b889b9cc74f5098da1", + "https://bcr.bazel.build/modules/cel-spec/0.15.0/MODULE.bazel": "e1eed53d233acbdcf024b4b0bc1528116d92c29713251b5154078ab1348cb600", + "https://bcr.bazel.build/modules/cel-spec/0.15.0/source.json": "ab7dccdf21ea2261c0f809b5a5221a4d7f8b580309f285fdf1444baaca75d44a", + "https://bcr.bazel.build/modules/civetweb/1.16/MODULE.bazel": "46a38f9daeb57392e3827fce7d40926be0c802bd23cdd6bfd3a96c804de42fae", + "https://bcr.bazel.build/modules/civetweb/1.16/source.json": "ba8b9585adb8355cb51b999d57172fd05e7a762c56b8d4bac6db42c99de3beb7", + "https://bcr.bazel.build/modules/curl/8.7.1/MODULE.bazel": "088221c35a2939c555e6e47cb31a81c15f8b59f4daa8009b1e9271a502d33485", + "https://bcr.bazel.build/modules/curl/8.8.0/MODULE.bazel": "7da3b3e79b0b4ee8f8c95d640bc6ad7b430ce66ef6e9c9d2bc29b3b5ef85f6fe", + "https://bcr.bazel.build/modules/curl/8.8.0/source.json": "d7d138b6878cf38891692fee0649ace35357fd549b425614d571786f054374d4", + "https://bcr.bazel.build/modules/cython/3.0.11-1/MODULE.bazel": "868b3f5c956c3657420d2302004c6bb92606bfa47e314bab7f2ba0630c7c966c", + "https://bcr.bazel.build/modules/cython/3.0.11-1/source.json": "da318be900b8ca9c3d1018839d3bebc5a8e1645620d0848fa2c696d4ecf7c296", + "https://bcr.bazel.build/modules/download_utils/1.0.1/MODULE.bazel": "f1d0afade59e37de978506d6bbf08d7fe5f94964e86944aaf58efcead827b41b", + "https://bcr.bazel.build/modules/download_utils/1.2.2/MODULE.bazel": "7d185ec9dd3c5ee277f269e3a8e5f09b9de4cb7ba34d06b93dce9bf41c1279f8", + "https://bcr.bazel.build/modules/download_utils/1.2.2/source.json": "c88be2bc48c98371d35665b805f307a647c98c83327345c918d9088822d77928", + "https://bcr.bazel.build/modules/envoy_api/0.0.0-20250128-4de3c74/MODULE.bazel": "1fe72489212c530086e3ffb0e018b2bfef4663200ca03571570f9f006bef1d75", + "https://bcr.bazel.build/modules/envoy_api/0.0.0-20250128-4de3c74/source.json": "028519164a2e24563f4b43d810fdedc702daed90e71e7042d45ba82ad807b46f", + "https://bcr.bazel.build/modules/flatbuffers/25.12.19/MODULE.bazel": "fe3a7f7811f43264f68136ad99e64384d70b2a25245e09ab800c4bb83171da25", + "https://bcr.bazel.build/modules/flatbuffers/25.12.19/source.json": "ea0204be7a79de9141cee5fa436e58a14e88b39b5b59227b21efa0394474ebea", + "https://bcr.bazel.build/modules/flatbuffers/25.2.10/MODULE.bazel": "dab15cafe8512d2c4a8daa44c2d7968c5c79f01e220d40076cdc260bf58605e2", + "https://bcr.bazel.build/modules/gazelle/0.27.0/MODULE.bazel": "3446abd608295de6d90b4a8a118ed64a9ce11dcb3dda2dc3290a22056bd20996", + "https://bcr.bazel.build/modules/gazelle/0.30.0/MODULE.bazel": "f888a1effe338491f35f0e0e85003b47bb9d8295ccba73c37e07702d8d31c65b", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", + "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", + "https://bcr.bazel.build/modules/gazelle/0.37.0/MODULE.bazel": "d1327ba0907d0275ed5103bfbbb13518f6c04955b402213319d0d6c0ce9839d4", + "https://bcr.bazel.build/modules/gazelle/0.37.0/source.json": "b3adc10e2394e7f63ea88fb1d622d4894bfe9ec6961c493ae9a887723ab16831", + "https://bcr.bazel.build/modules/google_benchmark/1.8.4/MODULE.bazel": "c6d54a11dcf64ee63545f42561eda3fd94c1b5f5ebe1357011de63ae33739d5e", + "https://bcr.bazel.build/modules/google_benchmark/1.9.5/MODULE.bazel": "8a85cfd90b1e45e6e68f1aa2aa9efce3c04add57df732571d7fd54c07e7c5143", + "https://bcr.bazel.build/modules/google_benchmark/1.9.5/source.json": "0bd357fd9db30ee31d5eb4c78b1086ce3d79b4423ce76de19e8a2fa7b2fa2e10", + "https://bcr.bazel.build/modules/googleapis/0.0.0-20240326-1c8d509c5/MODULE.bazel": "a4b7e46393c1cdcc5a00e6f85524467c48c565256b22b5fae20f84ab4a999a68", + "https://bcr.bazel.build/modules/googleapis/0.0.0-20240819-fe8ba054a/MODULE.bazel": "117b7c7be7327ed5d6c482274533f2dbd78631313f607094d4625c28203cacdf", + "https://bcr.bazel.build/modules/googleapis/0.0.0-20240819-fe8ba054a/source.json": "b31fc7eb283a83f71d2e5bfc3d1c562d2994198fa1278409fbe8caec3afc1d3e", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", + "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", + "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.2/MODULE.bazel": "827f54f492a3ce549c940106d73de332c2b30cebd0c20c0bc5d786aba7f116cb", + "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.2/source.json": "3664514073a819992320ffbce5825e4238459df344d8b01748af2208f8d2e1eb", + "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", + "https://bcr.bazel.build/modules/grpc-java/1.62.2/MODULE.bazel": "99b8771e8c7cacb130170fed2a10c9e8fed26334a93e73b42d2953250885a158", + "https://bcr.bazel.build/modules/grpc-java/1.66.0/MODULE.bazel": "86ff26209fac846adb89db11f3714b3dc0090fb2fb81575673cc74880cda4e7e", + "https://bcr.bazel.build/modules/grpc-java/1.66.0/source.json": "f841b339ff8516c86c3a5272cd053194dd0cb2fdd63157123835e1157a28328d", + "https://bcr.bazel.build/modules/grpc-proto/0.0.0-20240627-ec30f58/MODULE.bazel": "88de79051e668a04726e9ea94a481ec6f1692086735fd6f488ab908b3b909238", + "https://bcr.bazel.build/modules/grpc-proto/0.0.0-20240627-ec30f58/source.json": "5035d379c61042930244ab59e750106d893ec440add92ec0df6a0098ca7f131d", + "https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f", + "https://bcr.bazel.build/modules/jq.bzl/0.1.0/source.json": "746bf13cac0860f091df5e4911d0c593971cd8796b5ad4e809b2f8e133eee3d5", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", + "https://bcr.bazel.build/modules/libpfm/4.11.0.bcr.1/MODULE.bazel": "e5362dadc90aab6724c83a2cc1e67cbed9c89a05d97fb1f90053c8deb1e445c8", + "https://bcr.bazel.build/modules/libpfm/4.11.0.bcr.1/source.json": "0646414d9037f8aad148781dd760bec90b0b25ac12fda5e03f8aadbd6b9c61e6", + "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/mbedtls/3.6.0/MODULE.bazel": "8e380e4698107c5f8766264d4df92e36766248447858db28187151d884995a09", + "https://bcr.bazel.build/modules/mbedtls/3.6.0/source.json": "1dbe7eb5258050afcc3806b9d43050f71c6f539ce0175535c670df606790b30c", + "https://bcr.bazel.build/modules/nlohmann_json/3.11.3/MODULE.bazel": "87023db2f55fc3a9949c7b08dc711fae4d4be339a80a99d04453c4bb3998eefc", + "https://bcr.bazel.build/modules/nlohmann_json/3.11.3/source.json": "296c63a90c6813e53b3812d24245711981fc7e563d98fe15625f55181494488a", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", + "https://bcr.bazel.build/modules/opencensus-cpp/0.0.0-20230502-50eb5de.bcr.2/MODULE.bazel": "cc18734138dd18c912c6ce2a59186db28f85d8058c99c9f21b46ca3e0aba0ebe", + "https://bcr.bazel.build/modules/opencensus-cpp/0.0.0-20230502-50eb5de.bcr.2/source.json": "7c135f9d42bb3b045669c3c6ab3bb3c208e00b46aca4422eea64c29811a5b240", + "https://bcr.bazel.build/modules/opencensus-proto/0.4.1.bcr.2/MODULE.bazel": "789706a714855f92c5c8cfcf1ef32bbb64dcd3b7c9066756ad7986ec59709d29", + "https://bcr.bazel.build/modules/opencensus-proto/0.4.1.bcr.2/source.json": "aadf3f53e08b72376506b7c4ea3d167010c9efb160d7d6e1e304ed646bac1b36", + "https://bcr.bazel.build/modules/opencensus-proto/0.4.1/MODULE.bazel": "4a2e8b4d0b544002502474d611a5a183aa282251e14f6a01afe841c0c1b10372", + "https://bcr.bazel.build/modules/openssl/3.3.1.bcr.1/MODULE.bazel": "49c0c07e8fb87b480bccb842cfee1b32617f11dac590f732573c69058699a3d1", + "https://bcr.bazel.build/modules/openssl/3.3.1.bcr.1/source.json": "0c0872e048bbea052a9c541fb47019481a19201ba5555a71d762ad591bf94e1f", + "https://bcr.bazel.build/modules/opentelemetry-cpp/1.19.0/MODULE.bazel": "3455326c08b28415648a3d60d8e3c811847ebdbe64474f75b25878f25585aea1", + "https://bcr.bazel.build/modules/opentelemetry-cpp/1.19.0/source.json": "4e48137e4c3ecb99401ff99876df8fa330598d7da051869bec643446e8a8ff95", + "https://bcr.bazel.build/modules/opentelemetry-proto/1.4.0.bcr.1/MODULE.bazel": "5ceaf25e11170d22eded4c8032728b4a3f273765fccda32f9e94f463755c4167", + "https://bcr.bazel.build/modules/opentelemetry-proto/1.5.0/MODULE.bazel": "7543d91a53b98e7b5b37c5a0865b93bff12c1ee022b1e322cd236b968894b030", + "https://bcr.bazel.build/modules/opentelemetry-proto/1.5.0/source.json": "046b721ce203e88cdaad44d7dd17a86b7200eab9388b663b234e72e13ff7b143", + "https://bcr.bazel.build/modules/opentracing-cpp/1.6.0/MODULE.bazel": "b3925269f63561b8b880ae7cf62ccf81f6ece55b62cd791eda9925147ae116ec", + "https://bcr.bazel.build/modules/opentracing-cpp/1.6.0/source.json": "da1cb1add160f5e5074b7272e9db6fd8f1b3336c15032cd0a653af9d2f484aed", + "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", + "https://bcr.bazel.build/modules/package_metadata/0.0.2/source.json": "e53a759a72488d2c0576f57491ef2da0cf4aab05ac0997314012495935531b73", + "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", + "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/prometheus-cpp/1.3.0.bcr.1/MODULE.bazel": "116ad46e97c1d2aeb020fe2899a342a7e703574ce7c0faf7e4810f938c974a9a", + "https://bcr.bazel.build/modules/prometheus-cpp/1.3.0.bcr.1/source.json": "e813cce2d450708cfcb26e309c5172583a7440776edf354e83e6788c768e5cca", + "https://bcr.bazel.build/modules/prometheus-cpp/1.3.0/MODULE.bazel": "ce82e086bbc0b60267e970f6a54b2ca6d0f22d3eb6633e00e2cc2899c700f3d8", + "https://bcr.bazel.build/modules/protoc-gen-validate/1.0.4.bcr.2/MODULE.bazel": "c4bd2c850211ff5b7dadf9d2d0496c1c922fdedc303c775b01dfd3b3efc907ed", + "https://bcr.bazel.build/modules/protoc-gen-validate/1.0.4/MODULE.bazel": "b8913c154b16177990f6126d2d2477d187f9ddc568e95ee3e2d50fc65d2c494a", + "https://bcr.bazel.build/modules/protoc-gen-validate/1.2.1.bcr.1/MODULE.bazel": "4bf09676b62fa587ae07e073420a76ec8766dcce7545e5f8c68cfa8e484b5120", + "https://bcr.bazel.build/modules/protoc-gen-validate/1.2.1.bcr.1/source.json": "c19071ebc4b53b5f1cfab9c66eefaf6e4179eb8a998970d07b1077687e777f29", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", + "https://bcr.bazel.build/modules/pybind11_bazel/2.13.6/MODULE.bazel": "2d746fda559464b253b2b2e6073cb51643a2ac79009ca02100ebbc44b4548656", + "https://bcr.bazel.build/modules/pybind11_bazel/2.13.6/source.json": "6aa0703de8efb20cc897bbdbeb928582ee7beaf278bcd001ac253e1605bddfae", + "https://bcr.bazel.build/modules/rapidjson/1.1.0.bcr.20241007/MODULE.bazel": "82fbcb2e42f9e0040e76ccc74c06c3e46dfd33c64ca359293f8b84df0e6dff4c", + "https://bcr.bazel.build/modules/rapidjson/1.1.0.bcr.20241007/source.json": "5c42389ad0e21fc06b95ad7c0b730008271624a2fa3292e0eab5f30e15adeee3", + "https://bcr.bazel.build/modules/re2/2021-09-01/MODULE.bazel": "bcb6b96f3b071e6fe2d8bed9cc8ada137a105f9d2c5912e91d27528b3d123833", + "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2024-05-01/MODULE.bazel": "55a3f059538f381107824e7d00df5df6d061ba1fb80e874e4909c0f0549e8f3e", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a", + "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", + "https://bcr.bazel.build/modules/re2/2025-08-12.bcr.1/MODULE.bazel": "e09b434b122bfb786a69179f9b325e35cb1856c3f56a7a81dd61609260ed46e1", + "https://bcr.bazel.build/modules/re2/2025-08-12.bcr.1/source.json": "a8ae7c09533bf67f9f6e5122d884d5741600b09d78dca6fc0f2f8d2ee0c2d957", + "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/source.json": "d8b5fe461272018cc07cfafce11fe369c7525330804c37eec5a82f84cd475366", + "https://bcr.bazel.build/modules/rules_buf/0.1.1/MODULE.bazel": "6189aec18a4f7caff599ad41b851ab7645d4f1e114aa6431acf9b0666eb92162", + "https://bcr.bazel.build/modules/rules_buf/0.1.1/source.json": "021363d254f7438f3f10725355969c974bb2c67e0c28667782ade31a9cdb747f", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.5/MODULE.bazel": "be41f87587998fe8890cd82ea4e848ed8eb799e053c224f78f3ff7fe1a1d9b74", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", + "https://bcr.bazel.build/modules/rules_cc/0.1.4/MODULE.bazel": "bb03a452a7527ac25a7518fb86a946ef63df860b9657d8323a0c50f8504fb0b9", + "https://bcr.bazel.build/modules/rules_cc/0.2.14/MODULE.bazel": "353c99ed148887ee89c54a17d4100ae7e7e436593d104b668476019023b58df8", + "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", + "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", + "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", + "https://bcr.bazel.build/modules/rules_diff/1.0.0/MODULE.bazel": "1739509d8db9a6cd7d3584822340d3dfe1f9f27e62462fbca60aa061d88741b2", + "https://bcr.bazel.build/modules/rules_diff/1.0.0/source.json": "fc3824aed007b4db160ffb994036c6e558550857b6634a8e9ccee3e74c659312", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.10.1/MODULE.bazel": "b9527010e5fef060af92b6724edb3691970a5b1f76f74b21d39f7d433641be60", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.15.1/MODULE.bazel": "c2c60d26c79fda484acb95cdbec46e89d6b28b4845cb277160ce1e0c8622bb88", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.15.1/source.json": "a161811a63ba8a859086da3b7ff3ad04f2e9c255d7727b41087103fc0eb22f55", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://bcr.bazel.build/modules/rules_go/0.33.0/MODULE.bazel": "a2b11b64cd24bf94f57454f53288a5dacfe6cb86453eee7761b7637728c1910c", + "https://bcr.bazel.build/modules/rules_go/0.38.1/MODULE.bazel": "fb8e73dd3b6fc4ff9d260ceacd830114891d49904f5bda1c16bc147bcc254f71", + "https://bcr.bazel.build/modules/rules_go/0.39.1/MODULE.bazel": "d34fb2a249403a5f4339c754f1e63dc9e5ad70b47c5e97faee1441fc6636cd61", + "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", + "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", + "https://bcr.bazel.build/modules/rules_go/0.45.1/MODULE.bazel": "6d7884f0edf890024eba8ab31a621faa98714df0ec9d512389519f0edff0281a", + "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", + "https://bcr.bazel.build/modules/rules_go/0.48.0/MODULE.bazel": "d00ebcae0908ee3f5e6d53f68677a303d6d59a77beef879598700049c3980a03", + "https://bcr.bazel.build/modules/rules_go/0.50.1/MODULE.bazel": "b91a308dc5782bb0a8021ad4330c81fea5bda77f96b9e4c117b9b9c8f6665ee0", + "https://bcr.bazel.build/modules/rules_go/0.50.1/source.json": "205765fd30216c70321f84c9a967267684bdc74350af3f3c46c857d9f80a4fa2", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/5.5.0/MODULE.bazel": "486ad1aa15cdc881af632b4b1448b0136c76025a1fe1ad1b65c5899376b83a50", + "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", + "https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963", + "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", + "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", + "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", + "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.11.0/MODULE.bazel": "c3d280bc5ff1038dcb3bacb95d3f6b83da8dd27bba57820ec89ea4085da767ad", + "https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761", + "https://bcr.bazel.build/modules/rules_java/8.13.0/MODULE.bazel": "0444ebf737d144cf2bb2ccb368e7f1cce735264285f2a3711785827c1686625e", + "https://bcr.bazel.build/modules/rules_java/8.15.1/MODULE.bazel": "5071eebf0fd602ab0617f846e0e0d8f388d66c961513c736e0ac4a1dcde3ff2c", + "https://bcr.bazel.build/modules/rules_java/8.15.1/source.json": "e48286d5819767bc5b3d457539ae7f94e28a9b3e55d092d5c47176cb6a2a289b", + "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", + "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", + "https://bcr.bazel.build/modules/rules_java/8.6.3/MODULE.bazel": "e90505b7a931d194245ffcfb6ff4ca8ef9d46b4e830d12e64817752e0198e2ed", + "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", + "https://bcr.bazel.build/modules/rules_jvm_external/6.0/MODULE.bazel": "37c93a5a78d32e895d52f86a8d0416176e915daabd029ccb5594db422e87c495", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", + "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_multirun/0.9.0/MODULE.bazel": "32d628ef586b5b23f67e55886b7bc38913ea4160420d66ae90521dda2ff37df0", + "https://bcr.bazel.build/modules/rules_multirun/0.9.0/source.json": "e882ba77962fa6c5fe68619e5c7d0374ec9a219fb8d03c42eadaf6d0243771bd", + "https://bcr.bazel.build/modules/rules_multitool/0.4.0/MODULE.bazel": "15517987d5c00c9e7faab41fbe22ee67a350b6eabcc1e08baded5c6d9025897f", + "https://bcr.bazel.build/modules/rules_multitool/1.2.0/MODULE.bazel": "8d818d6104f4030930291bbbbc5684702c237dbcdee7229097543e6a6035adaa", + "https://bcr.bazel.build/modules/rules_multitool/1.9.0/MODULE.bazel": "8a042b0dbf35e4aaa94c28ad69efa75c9e673e9ea4bd5c0fb70bab75ef9c636b", + "https://bcr.bazel.build/modules/rules_multitool/1.9.0/source.json": "d9a01604a8b5c4a0e9430824dd34ca5b1b3f5b25277b755e8f3ae91f2c9362a3", + "https://bcr.bazel.build/modules/rules_nodejs/5.8.2/MODULE.bazel": "6bc03c8f37f69401b888023bf511cb6ee4781433b0cb56236b2e55a21e3a026a", + "https://bcr.bazel.build/modules/rules_nodejs/6.2.0/MODULE.bazel": "ec27907f55eb34705adb4e8257952162a2d4c3ed0f0b3b4c3c1aad1fac7be35e", + "https://bcr.bazel.build/modules/rules_nodejs/6.3.0/MODULE.bazel": "45345e4aba35dd6e4701c1eebf5a4e67af4ed708def9ebcdc6027585b34ee52d", + "https://bcr.bazel.build/modules/rules_nodejs/6.3.3/MODULE.bazel": "b66eadebd10f1f1b25f52f95ab5213a57e82c37c3f656fcd9a57ad04d2264ce7", + "https://bcr.bazel.build/modules/rules_nodejs/6.3.3/source.json": "45bd343155bdfed2543f0e39b80ff3f6840efc31975da4b5795797f4c94147ad", + "https://bcr.bazel.build/modules/rules_perl/0.2.4/MODULE.bazel": "5f5af7be4bf5fb88d91af7469518f0fd2161718aefc606188f7cd51f436ca938", + "https://bcr.bazel.build/modules/rules_perl/0.2.4/source.json": "574317d6b3c7e4843fe611b76f15e62a1889949f5570702e1ee4ad335ea3c339", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", + "https://bcr.bazel.build/modules/rules_pkg/1.1.0/MODULE.bazel": "9db8031e71b6ef32d1846106e10dd0ee2deac042bd9a2de22b4761b0c3036453", + "https://bcr.bazel.build/modules/rules_pkg/1.1.0/source.json": "fef768df13a92ce6067e1cd0cdc47560dace01354f1d921cfb1d632511f7d608", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc2/MODULE.bazel": "e17f94f8a347e2c808517b65d74988839d2d62daceb50073e44060193b785eb1", + "https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f", + "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", + "https://bcr.bazel.build/modules/rules_python/1.5.4/MODULE.bazel": "05425c7860cecf5dbcbfe30f3c9275453aff8f105649cf3fe65b7bc03290272c", + "https://bcr.bazel.build/modules/rules_python/1.5.4/source.json": "6a41f64aaffd7b548b1a3de7a732bf0eda1911974698627a29b7b305b1993162", + "https://bcr.bazel.build/modules/rules_rust/0.61.0/MODULE.bazel": "0318a95777b9114c8740f34b60d6d68f9cfef61e2f4b52424ca626213d33787b", + "https://bcr.bazel.build/modules/rules_rust/0.61.0/source.json": "d1bc743b5fa2e2abb35c436df7126a53dab0c3f35890ae6841592b2253786a63", + "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://bcr.bazel.build/modules/rules_shell/0.4.0/MODULE.bazel": "0f8f11bb3cd11755f0b48c1de0bbcf62b4b34421023aa41a2fc74ef68d9584f0", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", + "https://bcr.bazel.build/modules/rules_shell/0.5.0/MODULE.bazel": "8c8447370594d45539f66858b602b0bb2cb2d3401a4ebb9ad25830c59c0f366d", + "https://bcr.bazel.build/modules/rules_shell/0.6.0/MODULE.bazel": "c65e3ab217f64c3960e3ab55a53b430babcac6f0870fe79192812ae68a596a81", + "https://bcr.bazel.build/modules/rules_shell/0.6.0/source.json": "9431501bbc2114effd3b625b30555c5de51b7d291c1aee48b6f4d09d82126b3e", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", + "https://bcr.bazel.build/modules/stardoc/0.5.0/MODULE.bazel": "f9f1f46ba8d9c3362648eea571c6f9100680efc44913618811b58cc9c02cd678", + "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.5.4/MODULE.bazel": "6569966df04610b8520957cb8e97cf2e9faac2c0309657c537ab51c16c18a2a4", + "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", + "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd", + "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", + "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", + "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", + "https://bcr.bazel.build/modules/tar.bzl/0.2.1/source.json": "600ac6ff61744667a439e7b814ae59c1f29632c3984fccf8000c64c9db8d7bb6", + "https://bcr.bazel.build/modules/toolchain_utils/1.0.2/MODULE.bazel": "9b8be503a4fcfd3b8b952525bff0869177a5234d5c35dc3e566b9f5ca2f755a1", + "https://bcr.bazel.build/modules/toolchain_utils/1.0.2/source.json": "88769ec576dddacafd8cca4631812cf8eead89f10a29d9405d9f7a553de6bf87", + "https://bcr.bazel.build/modules/toolchains_protoc/0.2.1/MODULE.bazel": "2f08433ff5e659069b3a1abfee2377d68f510f2de1da50678ed992c455b4ff91", + "https://bcr.bazel.build/modules/xds/0.0.0-20240423-555b57e/MODULE.bazel": "cea509976a77e34131411684ef05a1d6ad194dd71a8d5816643bc5b0af16dc0f", + "https://bcr.bazel.build/modules/xds/0.0.0-20240423-555b57e/source.json": "7227e1fcad55f3f3cab1a08691ecd753cb29cc6380a47bc650851be9f9ad6d20", + "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", + "https://bcr.bazel.build/modules/yq.bzl/0.1.1/source.json": "2d2bad780a9f2b9195a4a370314d2c17ae95eaa745cefc2e12fbc49759b15aa3", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.1/MODULE.bazel": "6a9fe6e3fc865715a7be9823ce694ceb01e364c35f7a846bf0d2b34762bc066b", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/ape/1.0.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/apple_support/1.11.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/apple_support/1.15.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/apple_support/1.17.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/apple_support/1.21.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/apple_support/1.22.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/1.31.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/1.38.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/1.42.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.11.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.16.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.20.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.9.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.9.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_esbuild/0.21.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_js/1.33.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_js/1.40.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_js/2.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_js/2.1.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_js/2.3.8/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/0.12.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/1.0.0-rc9/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/1.0.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/1.3.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/1.4.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/1.4.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/1.5.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_py/1.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_py/1.4.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_ts/3.4.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_ts/3.6.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_tools_telemetry/0.2.5/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/0.1.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.1.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.1.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.10.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.11.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.13.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.15.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.17.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.18.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.19.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.2.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.21.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.23.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.27.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.28.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.30.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.4.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.9.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_features/1.9.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.0.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.1.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.2.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.2.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.3.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.4.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.4.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.5.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.6.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.7.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.8.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/bazel_skylib/1.9.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.assert/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.config/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.container/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.container_hash/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.core/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.describe/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.interprocess/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.intrusive/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.move/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.mp11/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.predef/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.preprocessor/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.static_assert/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.throw_exception/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.tuple/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.type_traits/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.unordered/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost.winapi/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boost/1.83.0.bcr.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boringssl/0.0.0-20230215-5c22014/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boringssl/0.0.0-20240530-2db0eb3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boringssl/0.20240913.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/boringssl/0.20241024.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/buildifier_prebuilt/6.1.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/buildifier_prebuilt/6.4.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/buildifier_prebuilt/7.3.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/buildifier_prebuilt/8.2.0.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/buildozer/7.1.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/c-ares/1.19.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/cel-spec/0.15.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/civetweb/1.16/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/curl/8.7.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/curl/8.8.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/cython/3.0.11-1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/download_utils/1.0.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/download_utils/1.2.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/envoy_api/0.0.0-20250128-4de3c74/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/flatbuffers/25.12.19/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/flatbuffers/25.2.10/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.27.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.30.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.32.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.33.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.34.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.36.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/gazelle/0.37.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/google_benchmark/1.8.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/google_benchmark/1.9.5/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googleapis/0.0.0-20240326-1c8d509c5/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googleapis/0.0.0-20240819-fe8ba054a/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.15.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.17.0.bcr.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.17.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/grpc-java/1.62.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/grpc-java/1.66.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/grpc-proto/0.0.0-20240627-ec30f58/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/jq.bzl/0.1.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/jsoncpp/1.9.6/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/libpfm/4.11.0.bcr.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/libpfm/4.11.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/mbedtls/3.6.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/nlohmann_json/3.11.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/nlohmann_json/3.6.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/opencensus-cpp/0.0.0-20230502-50eb5de.bcr.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/opencensus-proto/0.4.1.bcr.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/opencensus-proto/0.4.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/openssl/3.3.1.bcr.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/opentelemetry-cpp/1.19.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/opentelemetry-proto/1.4.0.bcr.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/opentelemetry-proto/1.5.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/opentracing-cpp/1.6.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/package_metadata/0.0.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.10/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.11/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.5/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.6/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.7/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.8/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/0.0.9/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/platforms/1.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/prometheus-cpp/1.3.0.bcr.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/prometheus-cpp/1.3.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protoc-gen-validate/1.0.4.bcr.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protoc-gen-validate/1.0.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/protoc-gen-validate/1.2.1.bcr.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/pybind11_bazel/2.11.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/pybind11_bazel/2.12.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/pybind11_bazel/2.13.6/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rapidjson/1.1.0.bcr.20241007/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/re2/2021-09-01/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/re2/2023-09-01/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/re2/2024-05-01/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/re2/2024-07-02/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/re2/2025-08-12.bcr.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_android/0.1.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_apple/3.16.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_buf/0.1.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.10/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.13/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.15/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.16/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.17/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.5/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.8/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.0.9/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.1.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.1.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.1.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.14/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.16/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.17/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_cc/0.2.8/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_diff/1.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_foreign_cc/0.10.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_foreign_cc/0.15.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.33.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.38.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.39.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.41.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.42.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.45.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.46.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.48.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_go/0.50.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/4.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/5.3.5/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/5.5.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/6.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/6.3.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/6.4.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/7.10.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/7.12.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/7.2.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/7.3.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/7.6.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.11.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.12.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.13.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.15.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.5.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.6.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_java/8.6.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_jvm_external/5.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_jvm_external/5.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_jvm_external/6.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_jvm_external/6.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_jvm_external/6.7/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_kotlin/1.9.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_kotlin/1.9.6/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_license/0.0.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_license/0.0.7/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_license/1.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_multirun/0.9.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_multitool/0.4.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_multitool/1.2.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_multitool/1.9.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/5.8.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/6.2.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/6.3.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/6.3.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_perl/0.2.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/0.7.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/1.0.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/1.1.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/4.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/6.0.0-rc2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/6.0.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/6.0.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/7.0.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/1.5.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.61.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.2.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.3.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.4.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.4.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.5.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.6.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_swift/3.1.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_baselibs/0.2.7/MODULE.bazel": "7383c9e593051051d128ca34be078abb365e14c2b12a15237f451240fc27ad73", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_baselibs/0.2.7/source.json": "70b750a4582db620d562d53735f9212e8d1b7486491e65293ca06035f20b6a74", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_cpp_toolchains/0.3.1/MODULE.bazel": "64d0466d8c113d4d1d365a7937b4c527bdd96f68a597678ce262edd0c2b8ebb0", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_cpp_toolchains/0.3.1/source.json": "0b3297f7cb86cc52ee8dfc2451c5dc9d87c75f25de8c6a01af2fdf9543302996", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_platforms/0.1.1/MODULE.bazel": "236e5bdff6f2d6de6f96cc4f5f4b1bf2cd6137547ce279668a2dd4c54cd4236c", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_platforms/0.1.2/MODULE.bazel": "d1889bf36241521c5d5c401aaf1e98242a60f9cab9d223f8190e5d7087956e83", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_bazel_platforms/0.1.2/source.json": "549c18a968c1ab76135ad38fd15981fcc04d377a62f20e2e5e0b5faa9de2b01d", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_cr_checker/0.2.2/MODULE.bazel": "dc36d9c35543db918c3fb5b93a8e684431f56c7c784cf2a1b90f35802a373c98", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_cr_checker/0.3.1/MODULE.bazel": "f49e037d7fbc0b2a8b2734fc6b47334e8cc8589ca7a5aa0f3ccca85cc5f79fac", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_dash_license_checker/0.1.1/MODULE.bazel": "76681dbd2d45b5c540869a2337174086c56c54953aab1d02cd878b59d31d13a5", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/0.2.4/MODULE.bazel": "ea4801e96c87e2b8650a0fa9e5fed9b8bdbef05c1bc3e30003ba527d5af60a43", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/0.2.6/MODULE.bazel": "1af2963e91c6472555e222f0aba3dc2f5492d04598298209a361978ee3e321e3", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/0.3.3/MODULE.bazel": "95d2b7d44d461c1cf9bd016605f740716fd4ea1303f5f2ed93de3566b90feb1b", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/1.0.0-RC1/MODULE.bazel": "e118b5cbdc453cde83b5ce481107d8e4a713d3458550b9d10445046d4bba2ff3", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/1.0.0/MODULE.bazel": "a3ad204b7412c02a899034d78de62b5549bafba5530a256d1007cb3f4ed20a11", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/1.0.2-CW1/MODULE.bazel": "08d30eba6a1c502096abd65ebb759afb35f988b31e4b72ab5866177703cfec77", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/1.0.2/MODULE.bazel": "259894df09b4aa6bb5152717b03c3c06a3fd4ad583a7119c52f3a2f0d277b857", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/1.2.0/MODULE.bazel": "a2b10950d585e14b09a6266025c0624b42101f72d3c4efe9591716b8713ede47", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/2.0.0/MODULE.bazel": "0c850a488fd50067b28726bfd7330a6970e36b63e67ea06efd5fbbc813e7be5c", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/2.0.3/MODULE.bazel": "9b945514727190d4c381d8965b972884ba04ce105260ffd2b3c9df51f206ebfe", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/2.2.0/MODULE.bazel": "467d9b7f70f3c4f9ba84b5e9718da0272dcf8e30a737173bf79a48f017927744", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/2.2.0/source.json": "176af08bcfa30f5857cc6bcdd7fc2d737857d3cd8bc718c09544dba2cbba7d56", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_format_checker/0.1.1/MODULE.bazel": "1acc254faa90e9f97b79ac69af25b6c21c561f8d6079914f6352b9b20d26bd37", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_itf/0.1.0/MODULE.bazel": "8f55aee8c543af4b862e42a0abb3bfa0560078f84816d4028ef9bb264ea1d498", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_itf/0.1.0/source.json": "da726115008d11fe6b911c8bc0b92a35a79127321964d049bcf661e1d6082fb0", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_platform/0.1.0/MODULE.bazel": "cc9eae86e76f2a930510ed6e50ec991bb5661687e24881685b39c322087adf6f", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_platform/0.1.1/MODULE.bazel": "eb086ba99f9319371fbbd0a9252dfd27b0817039b88bd4d691602974b1ada005", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.0.4/MODULE.bazel": "f74302cb90a7c4878db302276afae82966878099861dcfca3ef43256131dab52", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.0.5/MODULE.bazel": "ed17c232ebd65e9d50fd5c1832f90f95ffe95b2a1113d63a176295a2af64d111", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.1.0/MODULE.bazel": "97dd927309f87ecb73629725683028a5dbb37a49b1159c771292e6993569055b", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.1.1-Beta/MODULE.bazel": "7ab8931f4b2754b728101ed5a2afda458b82b885f5cfdb9d8c4c62b5c1a2da7e", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.1.1/MODULE.bazel": "35c84c7cec7d15678f63145054bb74367afb65929724c2115095d5e1b96fca94", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.2.0/MODULE.bazel": "ec092bf01731a1866352d7b8aaa0e30cae319667abc8cf7a86aec0bdb8555a0e", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.3.1/MODULE.bazel": "29666e38fbc76eddd6676e594f225e474d130dce9c3a9d224e59ae7a499c4575", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.3.2/MODULE.bazel": "a32390ef217cef9a811408b0a1c5aeed1398c377aa846f5d5416d7b95b4e4366", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.3.2/source.json": "213a66278c43fb27f8ca295e721bc05daed7ad9438f1173c7cc57cabd969de11", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.0/MODULE.bazel": "785ddd5295213e36c31ab86bdc34f29c0f7d1b72e9abd931bb08f42c0e48e2e9", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.1/MODULE.bazel": "99c491109937542e61df090222666a8613ef946fa7bb2b2d5ba648b2baba03ad", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.2/MODULE.bazel": "f25490f64035a0e3a0d53ad9cb6164e8325ce6cf2a7ee68c6ae153840cb2497e", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_python_basics/0.3.4/MODULE.bazel": "53bd16dfbb1fb8ecf6822fb26f9f4e8333bac7b14d12bb02bf84078063820a31", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rust_policies/0.0.2/MODULE.bazel": "ade2bad4a331b02d9b7e7d9842e8de8c6fded6186486e02c4f7db5cd4b71d34d", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_rust_policies/0.0.2/source.json": "fbcbc738e652b0c68d5d28dd1db09f2e643dc111f5739b2f6af7ec56c2e88043", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_starpls_lsp/0.1.0/MODULE.bazel": "b2f8c4c8d8e851706255ff9002b448bff6e040b8f0c6adedbde2a09375aa16cc", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_tooling/1.0.2/MODULE.bazel": "e70f396375b9d612b4f41ebceff7f18f68ab423b14625c138a354cc01bc62a10", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_tooling/1.1.2/MODULE.bazel": "56d08309931cfad67c2b6691207bb5f761a3946830d620c630d2436630e6b499", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_tooling/1.1.2/source.json": "f37e462ac9bb3bca49b944ed51e7b932b3822ff434a56c6cc5e1288ff5a9db01", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.5.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.5.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.5.4/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.5.6/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.6.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.7.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.7.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/tar.bzl/0.2.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/toolchain_utils/1.0.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/toolchains_protoc/0.2.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/xds/0.0.0-20240423-555b57e/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/yq.bzl/0.1.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/zlib/1.3.1.bcr.1/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/zlib/1.3.1/MODULE.bazel": "not found" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": { + "general": { + "bzlTransitiveDigest": "ybz6P4SJzMg5pnvcy5R+NjnVtaG28rmr5PciSWtOO3I=", + "usagesDigest": "Nu68r8mnzj8cnDyFSA+9w1D6Y8+c0qh7eJLvgI2K+bU=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_apple_cc_toolchains": { + "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf_toolchains", + "attributes": {} + }, + "local_config_apple_cc": { + "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "apple_support+", + "bazel_tools", + "bazel_tools" + ], + [ + "bazel_tools", + "rules_cc", + "rules_cc+" + ] + ] + } + }, + "@@aspect_rules_esbuild+//esbuild:extensions.bzl%esbuild": { + "general": { + "bzlTransitiveDigest": "r6vfRJ8YJFTpQ7IlfmhZlpmTcOwINqedvFcmwlCQra4=", + "usagesDigest": "sj4kz7yaVclWMuWhUhSLq0bVH7+HrkWyMdODMeA7Zhw=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "esbuild_darwin-x64": { + "repoRuleId": "@@aspect_rules_esbuild+//esbuild:repositories.bzl%esbuild_repositories", + "attributes": { + "esbuild_version": "0.19.9", + "platform": "darwin-x64" + } + }, + "esbuild_darwin-arm64": { + "repoRuleId": "@@aspect_rules_esbuild+//esbuild:repositories.bzl%esbuild_repositories", + "attributes": { + "esbuild_version": "0.19.9", + "platform": "darwin-arm64" + } + }, + "esbuild_linux-x64": { + "repoRuleId": "@@aspect_rules_esbuild+//esbuild:repositories.bzl%esbuild_repositories", + "attributes": { + "esbuild_version": "0.19.9", + "platform": "linux-x64" + } + }, + "esbuild_linux-arm64": { + "repoRuleId": "@@aspect_rules_esbuild+//esbuild:repositories.bzl%esbuild_repositories", + "attributes": { + "esbuild_version": "0.19.9", + "platform": "linux-arm64" + } + }, + "esbuild_win32-x64": { + "repoRuleId": "@@aspect_rules_esbuild+//esbuild:repositories.bzl%esbuild_repositories", + "attributes": { + "esbuild_version": "0.19.9", + "platform": "win32-x64" + } + }, + "esbuild_toolchains": { + "repoRuleId": "@@aspect_rules_esbuild+//esbuild/private:toolchains_repo.bzl%toolchains_repo", + "attributes": { + "esbuild_version": "0.19.9", + "user_repository_name": "esbuild" + } + }, + "npm__esbuild_0.19.9": { + "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_rule", + "attributes": { + "package": "esbuild", + "version": "0.19.9", + "root_package": "", + "link_workspace": "", + "link_packages": {}, + "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", + "url": "", + "commit": "", + "patch_args": [ + "-p0" + ], + "patches": [], + "custom_postinstall": "", + "npm_auth": "", + "npm_auth_basic": "", + "npm_auth_username": "", + "npm_auth_password": "", + "lifecycle_hooks": [], + "extra_build_content": "", + "generate_bzl_library_targets": false, + "extract_full_archive": false, + "exclude_package_contents": [], + "system_tar": "auto" + } + }, + "npm__esbuild_0.19.9__links": { + "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links", + "attributes": { + "package": "esbuild", + "version": "0.19.9", + "dev": false, + "root_package": "", + "link_packages": {}, + "deps": {}, + "transitive_closure": {}, + "lifecycle_build_target": false, + "lifecycle_hooks_env": [], + "lifecycle_hooks_execution_requirements": [ + "no-sandbox" + ], + "lifecycle_hooks_use_default_shell_env": false, + "bins": {}, + "package_visibility": [ + "//visibility:public" + ], + "replace_package": "", + "exclude_package_contents": [] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "aspect_bazel_lib+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_bazel_lib+", + "bazel_tools", + "bazel_tools" + ], + [ + "aspect_bazel_lib+", + "tar.bzl", + "tar.bzl+" + ], + [ + "aspect_rules_esbuild+", + "aspect_rules_js", + "aspect_rules_js+" + ], + [ + "aspect_rules_esbuild+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_rules_js+", + "aspect_bazel_lib", + "aspect_bazel_lib+" + ], + [ + "aspect_rules_js+", + "aspect_rules_js", + "aspect_rules_js+" + ], + [ + "aspect_rules_js+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_rules_js+", + "bazel_tools", + "bazel_tools" + ], + [ + "tar.bzl+", + "aspect_bazel_lib", + "aspect_bazel_lib+" + ], + [ + "tar.bzl+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "tar.bzl+", + "tar.bzl", + "tar.bzl+" + ] + ] + } + }, + "@@aspect_rules_py+//py:extensions.bzl%py_tools": { + "general": { + "bzlTransitiveDigest": "M7X/YAx3JXvEw5UpsjgI7BTFZsGgueETwFD8dINlH2Q=", + "usagesDigest": "NC1b49l5tenTBVWEUGzzC0j5Kg1GH+l5lBw5JRCldIU=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "bsd_tar_darwin_amd64": { + "repoRuleId": "@@aspect_bazel_lib+//lib/private:tar_toolchain.bzl%bsdtar_binary_repo", + "attributes": { + "platform": "darwin_amd64" + } + }, + "bsd_tar_darwin_arm64": { + "repoRuleId": "@@aspect_bazel_lib+//lib/private:tar_toolchain.bzl%bsdtar_binary_repo", + "attributes": { + "platform": "darwin_arm64" + } + }, + "bsd_tar_linux_amd64": { + "repoRuleId": "@@aspect_bazel_lib+//lib/private:tar_toolchain.bzl%bsdtar_binary_repo", + "attributes": { + "platform": "linux_amd64" + } + }, + "bsd_tar_linux_arm64": { + "repoRuleId": "@@aspect_bazel_lib+//lib/private:tar_toolchain.bzl%bsdtar_binary_repo", + "attributes": { + "platform": "linux_arm64" + } + }, + "bsd_tar_windows_amd64": { + "repoRuleId": "@@aspect_bazel_lib+//lib/private:tar_toolchain.bzl%bsdtar_binary_repo", + "attributes": { + "platform": "windows_amd64" + } + }, + "bsd_tar_windows_arm64": { + "repoRuleId": "@@aspect_bazel_lib+//lib/private:tar_toolchain.bzl%bsdtar_binary_repo", + "attributes": { + "platform": "windows_arm64" + } + }, + "bsd_tar_toolchains": { + "repoRuleId": "@@aspect_bazel_lib+//lib/private:tar_toolchain.bzl%tar_toolchains_repo", + "attributes": { + "user_repository_name": "bsd_tar" + } + }, + "rules_py_tools.darwin_amd64": { + "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:tools.bzl%prebuilt_tool_repo", + "attributes": { + "platform": "darwin_amd64" + } + }, + "rules_py_tools.darwin_arm64": { + "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:tools.bzl%prebuilt_tool_repo", + "attributes": { + "platform": "darwin_arm64" + } + }, + "rules_py_tools.linux_amd64": { + "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:tools.bzl%prebuilt_tool_repo", + "attributes": { + "platform": "linux_amd64" + } + }, + "rules_py_tools.linux_arm64": { + "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:tools.bzl%prebuilt_tool_repo", + "attributes": { + "platform": "linux_arm64" + } + }, + "rules_py_tools": { + "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:repo.bzl%toolchains_repo", + "attributes": { + "user_repository_name": "rules_py_tools" + } + }, + "rules_py_pex_2_3_1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "urls": [ + "https://files.pythonhosted.org/packages/e7/d0/fbda2a4d41d62d86ce53f5ae4fbaaee8c34070f75bb7ca009090510ae874/pex-2.3.1-py2.py3-none-any.whl" + ], + "sha256": "64692a5bf6f298403aab930d22f0d836ae4736c5bc820e262e9092fe8c56f830", + "downloaded_file_path": "pex-2.3.1-py2.py3-none-any.whl" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "aspect_bazel_lib+", + "bazel_tools", + "bazel_tools" + ], + [ + "aspect_rules_py+", + "aspect_bazel_lib", + "aspect_bazel_lib+" + ], + [ + "aspect_rules_py+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@aspect_tools_telemetry+//:extension.bzl%telemetry": { + "general": { + "bzlTransitiveDigest": "0KaOWQZ9BJwPbN2AUMBuXTWAUrFgNuSy4bQEdQO8TWg=", + "usagesDigest": "eRiCUsMblBEQYMowuRrFqHATLrp0/gw1Fl28aD0kJds=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "aspect_tools_telemetry_report": { + "repoRuleId": "@@aspect_tools_telemetry+//:extension.bzl%tel_repository", + "attributes": { + "deps": { + "aspect_rules_lint": "1.5.3", + "aspect_tools_telemetry": "0.2.5" + } + } + } + }, + "recordedRepoMappingEntries": [ + [ + "aspect_tools_telemetry+", + "aspect_bazel_lib", + "aspect_bazel_lib+" + ], + [ + "aspect_tools_telemetry+", + "bazel_skylib", + "bazel_skylib+" + ] + ] + } + }, + "@@googleapis+//:extensions.bzl%switched_rules": { + "general": { + "bzlTransitiveDigest": "vG6fuTzXD8MMvHWZEQud0MMH7eoC4GXY0va7VrFFh04=", + "usagesDigest": "z6Rsh4v/CM912ua5VTMry5Q1yUgdlDUhzI/xvNZdyVs=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "com_google_googleapis_imports": { + "repoRuleId": "@@googleapis+//:repository_rules.bzl%switched_rules", + "attributes": { + "rules": { + "proto_library_with_info": [ + "", + "" + ], + "moved_proto_library": [ + "", + "" + ], + "java_proto_library": [ + "", + "" + ], + "java_grpc_library": [ + "", + "" + ], + "java_gapic_library": [ + "", + "" + ], + "java_gapic_test": [ + "", + "" + ], + "java_gapic_assembly_gradle_pkg": [ + "", + "" + ], + "py_proto_library": [ + "", + "" + ], + "py_grpc_library": [ + "", + "" + ], + "py_gapic_library": [ + "", + "" + ], + "py_test": [ + "", + "" + ], + "py_gapic_assembly_pkg": [ + "", + "" + ], + "py_import": [ + "", + "" + ], + "go_proto_library": [ + "", + "" + ], + "go_grpc_library": [ + "", + "" + ], + "go_library": [ + "", + "" + ], + "go_test": [ + "", + "" + ], + "go_gapic_library": [ + "", + "" + ], + "go_gapic_assembly_pkg": [ + "", + "" + ], + "cc_proto_library": [ + "", + "" + ], + "cc_grpc_library": [ + "", + "" + ], + "cc_gapic_library": [ + "", + "" + ], + "php_proto_library": [ + "", + "php_proto_library" + ], + "php_grpc_library": [ + "", + "php_grpc_library" + ], + "php_gapic_library": [ + "", + "php_gapic_library" + ], + "php_gapic_assembly_pkg": [ + "", + "php_gapic_assembly_pkg" + ], + "nodejs_gapic_library": [ + "", + "typescript_gapic_library" + ], + "nodejs_gapic_assembly_pkg": [ + "", + "typescript_gapic_assembly_pkg" + ], + "ruby_proto_library": [ + "", + "" + ], + "ruby_grpc_library": [ + "", + "" + ], + "ruby_ads_gapic_library": [ + "", + "" + ], + "ruby_cloud_gapic_library": [ + "", + "" + ], + "ruby_gapic_assembly_pkg": [ + "", + "" + ], + "csharp_proto_library": [ + "", + "" + ], + "csharp_grpc_library": [ + "", + "" + ], + "csharp_gapic_library": [ + "", + "" + ], + "csharp_gapic_assembly_pkg": [ + "", + "" + ] + } + } + } + }, + "recordedRepoMappingEntries": [] + } + }, + "@@rules_buf+//buf:extensions.bzl%ext": { + "general": { + "bzlTransitiveDigest": "Fk4QK5kj/HKxKVGAfyZEJNUAOC+Ic+kKMcXb20jecDE=", + "usagesDigest": "RTc2BMQ2b0wGU8CRvN3EoPz34m3LMe+K/oSkFkN83+M=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "rules_buf_toolchains": { + "repoRuleId": "@@rules_buf+//buf/internal:toolchain.bzl%buf_download_releases", + "attributes": { + "version": "v1.27.0" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_buf+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "general": { + "bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=", + "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "com_github_jetbrains_kotlin_git": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", + "attributes": { + "urls": [ + "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" + ], + "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" + } + }, + "com_github_jetbrains_kotlin": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", + "attributes": { + "git_repository_name": "com_github_jetbrains_kotlin_git", + "compiler_version": "1.9.23" + } + }, + "com_github_google_ksp": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", + "attributes": { + "urls": [ + "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" + ], + "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", + "strip_version": "1.9.23-1.0.20" + } + }, + "com_github_pinterest_ktlint": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", + "urls": [ + "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" + ], + "executable": true + } + }, + "rules_android": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + "strip_prefix": "rules_android-0.1.1", + "urls": [ + "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_kotlin+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_nodejs+//nodejs:extensions.bzl%node": { + "general": { + "bzlTransitiveDigest": "q44Ox2Nwogn6OsO0Xw5lhjkd/xmxkvvpwVOn5P4pmHQ=", + "usagesDigest": "ov+dL/V0KVBmibdfkNwmoA4XB652OL3pgvzj2yp8+Yw=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "nodejs_linux_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "18.20.5", + "include_headers": false, + "platform": "linux_amd64" + } + }, + "nodejs_linux_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "18.20.5", + "include_headers": false, + "platform": "linux_arm64" + } + }, + "nodejs_linux_s390x": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "18.20.5", + "include_headers": false, + "platform": "linux_s390x" + } + }, + "nodejs_linux_ppc64le": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "18.20.5", + "include_headers": false, + "platform": "linux_ppc64le" + } + }, + "nodejs_darwin_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "18.20.5", + "include_headers": false, + "platform": "darwin_amd64" + } + }, + "nodejs_darwin_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "18.20.5", + "include_headers": false, + "platform": "darwin_arm64" + } + }, + "nodejs_windows_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "18.20.5", + "include_headers": false, + "platform": "windows_amd64" + } + }, + "nodejs": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_host": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_toolchains": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_toolchains_repo.bzl%nodejs_toolchains_repo", + "attributes": { + "user_node_repository_name": "nodejs" + } + } + }, + "recordedRepoMappingEntries": [] + } + }, + "@@rules_python+//python/uv:uv.bzl%uv": { + "general": { + "bzlTransitiveDigest": "bGHlxez0Lkvq2VwrlfCLraKHiJIRHSIJb432X2+pky8=", + "usagesDigest": "va9ETYwpdy25OvZdbZMHshDBpfvhJEyo+MjGA5UEBn4=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "uv": { + "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", + "attributes": { + "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", + "toolchain_names": [ + "none" + ], + "toolchain_implementations": { + "none": "'@@rules_python+//python:none'" + }, + "toolchain_compatible_with": { + "none": [ + "@platforms//:incompatible" + ] + }, + "toolchain_target_settings": {} + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_python+", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_python+", + "platforms", + "platforms" + ] + ] + } + }, + "@@score_bazel_cpp_toolchains+//extensions:gcc.bzl%gcc": { + "general": { + "bzlTransitiveDigest": "sSiH+6wYA9seKxUdXyYTUDxWBTage1O1lrx4+3s5RAE=", + "usagesDigest": "yrrYScjHrivYw0rSzIVU75lveZw793mAW0x6beTzwqY=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "score_gcc_x86_64_toolchain_pkg": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/eclipse-score/toolchains_gcc_packages/releases/download/v0.0.4/x86_64-unknown-linux-gnu_gcc12.tar.gz" + ], + "build_file": "@@score_bazel_cpp_toolchains+//packages/linux/x86_64/gcc/12.2.0:gcc.BUILD", + "sha256": "e9b9a7a63a5f8271b76d6e2057906b95c7a244e4931a8e10edeaa241e9f7c11e", + "strip_prefix": "x86_64-unknown-linux-gnu" + } + }, + "score_gcc_aarch64_toolchain_pkg": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/eclipse-score/toolchains_gcc_packages/releases/download/v0.0.4/aarch64-unknown-linux-gnu_gcc12.tar.gz" + ], + "build_file": "@@score_bazel_cpp_toolchains+//packages/linux/aarch64/gcc/12.2.0:gcc.BUILD", + "sha256": "7279b1adb50361b21f5266b001980b6febb35fa8d83170901196b9edae3f06d9", + "strip_prefix": "aarch64-unknown-linux-gnu" + } + }, + "score_qcc_x86_64_toolchain_pkg": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://www.qnx.com/download/download/79858/installation.tgz" + ], + "build_file": "@@score_bazel_cpp_toolchains+//packages/qnx/x86_64/sdp/8.0.0:sdp.BUILD", + "sha256": "f2e0cb21c6baddbcb65f6a70610ce498e7685de8ea2e0f1648f01b327f6bac63", + "strip_prefix": "installation" + } + }, + "score_qcc_aarch64_toolchain_pkg": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://www.qnx.com/download/download/79858/installation.tgz" + ], + "build_file": "@@score_bazel_cpp_toolchains+//packages/qnx/aarch64/sdp/8.0.0:sdp.BUILD", + "sha256": "f2e0cb21c6baddbcb65f6a70610ce498e7685de8ea2e0f1648f01b327f6bac63", + "strip_prefix": "installation" + } + }, + "score_gcc_x86_64_toolchain": { + "repoRuleId": "@@score_bazel_cpp_toolchains+//rules:gcc.bzl%gcc_toolchain", + "attributes": { + "extra_compile_flags": [], + "extra_c_compile_flags": [], + "extra_cxx_compile_flags": [], + "extra_link_flags": [], + "license_info_variable": "", + "license_info_value": "", + "license_path": "/opt/score_qnx/license/licenses", + "sdk_version": "", + "sdp_version": "", + "tc_cpu": "x86_64", + "tc_identifier": "gcc_12.2.0", + "tc_os": "linux", + "tc_pkg_repo": "score_gcc_x86_64_toolchain_pkg", + "tc_system_toolchain": false, + "tc_runtime_ecosystem": "", + "gcc_version": "12.2.0", + "cc_toolchain_config": "@@score_bazel_cpp_toolchains+//templates/linux:cc_toolchain_config.bzl.template", + "cc_toolchain_flags": "@@score_bazel_cpp_toolchains+//templates/linux:cc_toolchain_flags.bzl.template", + "use_base_constraints_only": false + } + }, + "score_gcc_aarch64_toolchain": { + "repoRuleId": "@@score_bazel_cpp_toolchains+//rules:gcc.bzl%gcc_toolchain", + "attributes": { + "extra_compile_flags": [], + "extra_c_compile_flags": [], + "extra_cxx_compile_flags": [], + "extra_link_flags": [], + "license_info_variable": "", + "license_info_value": "", + "license_path": "/opt/score_qnx/license/licenses", + "sdk_version": "", + "sdp_version": "", + "tc_cpu": "aarch64", + "tc_identifier": "gcc_12.2.0", + "tc_os": "linux", + "tc_pkg_repo": "score_gcc_aarch64_toolchain_pkg", + "tc_system_toolchain": false, + "tc_runtime_ecosystem": "", + "gcc_version": "12.2.0", + "cc_toolchain_config": "@@score_bazel_cpp_toolchains+//templates/linux:cc_toolchain_config.bzl.template", + "cc_toolchain_flags": "@@score_bazel_cpp_toolchains+//templates/linux:cc_toolchain_flags.bzl.template", + "use_base_constraints_only": false + } + }, + "score_qcc_x86_64_toolchain": { + "repoRuleId": "@@score_bazel_cpp_toolchains+//rules:gcc.bzl%gcc_toolchain", + "attributes": { + "extra_compile_flags": [], + "extra_c_compile_flags": [], + "extra_cxx_compile_flags": [], + "extra_link_flags": [], + "license_info_variable": "", + "license_info_value": "", + "license_path": "/opt/score_qnx/license/licenses", + "sdk_version": "", + "sdp_version": "8.0.0", + "tc_cpu": "x86_64", + "tc_identifier": "sdp_8.0.0", + "tc_os": "qnx", + "tc_pkg_repo": "score_qcc_x86_64_toolchain_pkg", + "tc_system_toolchain": false, + "tc_runtime_ecosystem": "", + "gcc_version": "12.2.0", + "cc_toolchain_config": "@@score_bazel_cpp_toolchains+//templates/qnx:cc_toolchain_config.bzl.template", + "cc_toolchain_flags": "@@score_bazel_cpp_toolchains+//templates/qnx:cc_toolchain_flags.bzl.template", + "use_base_constraints_only": false + } + }, + "score_qcc_aarch64_toolchain": { + "repoRuleId": "@@score_bazel_cpp_toolchains+//rules:gcc.bzl%gcc_toolchain", + "attributes": { + "extra_compile_flags": [], + "extra_c_compile_flags": [], + "extra_cxx_compile_flags": [], + "extra_link_flags": [], + "license_info_variable": "", + "license_info_value": "", + "license_path": "/opt/score_qnx/license/licenses", + "sdk_version": "", + "sdp_version": "8.0.0", + "tc_cpu": "aarch64", + "tc_identifier": "sdp_8.0.0", + "tc_os": "qnx", + "tc_pkg_repo": "score_qcc_aarch64_toolchain_pkg", + "tc_system_toolchain": false, + "tc_runtime_ecosystem": "", + "gcc_version": "12.2.0", + "cc_toolchain_config": "@@score_bazel_cpp_toolchains+//templates/qnx:cc_toolchain_config.bzl.template", + "cc_toolchain_flags": "@@score_bazel_cpp_toolchains+//templates/qnx:cc_toolchain_flags.bzl.template", + "use_base_constraints_only": false + } + } + }, + "recordedRepoMappingEntries": [ + [ + "bazel_tools", + "rules_cc", + "rules_cc+" + ], + [ + "score_bazel_cpp_toolchains+", + "bazel_tools", + "bazel_tools" + ], + [ + "score_bazel_cpp_toolchains+", + "score_bazel_cpp_toolchains", + "score_bazel_cpp_toolchains+" + ] + ] + } + } + } +} diff --git a/NOTICE b/NOTICE index 5d111d4..98e3216 100644 --- a/NOTICE +++ b/NOTICE @@ -1,3 +1,18 @@ + + # Notices for Eclipse Safe Open Vehicle Core This content is produced and maintained by the Eclipse Safe Open Vehicle Core project. diff --git a/README.md b/README.md index 1e8af75..0342325 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,17 @@ + # C++ & Rust Bazel Template Repository This repository serves as a **template** for setting up **C++ and Rust projects** using **Bazel**. @@ -17,16 +30,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,29 +88,27 @@ cd YOUR_PROJECT To build all targets of the module the following command can be used: ```sh -bazel build //src/... +# 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 ``` -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 -``` - -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 within devcontainer (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 ``` +Note: Run the `docker pull` command from a VS Code Terminal associated with the devcontainer. This properly sets up all environment variables, which may not be the case when just using docker to attach to the running container. + --- ## 🛠 Tools & Linters @@ -86,6 +125,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 +155,24 @@ 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`. + +## DevContainer Setup + +### Known Issue: Pre-commit Hook Not Running +**Problem:** The pre-commit hook does not run when using `git commit` inside the DevContainer. + +**Cause:** A stale `core.hooksPath` configuration overrides the default hook lookup path. + +**Fix:** Unset the custom hooks path: + +```bash +git config --unset core.hooksPath +``` + +Note: For a permanent fix, run this command on the **host machine** (outside the DevContainer). +The DevContainer only receives a copy of the host's Git configuration at build time, so changes +made inside the container will not persist after a rebuild. + +# 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/REUSE.toml b/REUSE.toml new file mode 100644 index 0000000..43801e8 --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,39 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* + +version = 1 + +[[annotations]] +path = [".devcontainer/*.json"] +SPDX-FileCopyrightText = "Copyright (c) 2026 Contributors to the Eclipse Foundation" +SPDX-License-Identifier = "Apache-2.0" + +[[annotations]] +path = [".vscode/*.json", ".vscode/restructuredtext.code-snippets"] +SPDX-FileCopyrightText = "Copyright (c) 2026 Contributors to the Eclipse Foundation" +SPDX-License-Identifier = "Apache-2.0" + +[[annotations]] +path = ["**/*.lock.json", "**/Cargo.lock", "MODULE.bazel.lock"] +SPDX-FileCopyrightText = "Copyright (c) 2026 Contributors to the Eclipse Foundation" +SPDX-License-Identifier = "Apache-2.0" + +[[annotations]] +path = [".github/templatesyncignore.txt"] +SPDX-FileCopyrightText = "Copyright (c) 2026 Contributors to the Eclipse Foundation" +SPDX-License-Identifier = "Apache-2.0" + +[[annotations]] +path = [".bazelversion"] +SPDX-FileCopyrightText = "Copyright (c) 2026 Contributors to the Eclipse Foundation" +SPDX-License-Identifier = "Apache-2.0" 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..a539592 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2024 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -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..e79a7ce --- /dev/null +++ b/docs/crypto/architecture/api_architecture.rst @@ -0,0 +1,61 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 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 + # ******************************************************************************* + +.. _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..4296d81 --- /dev/null +++ b/docs/crypto/architecture/api_description.rst @@ -0,0 +1,633 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 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 + # ******************************************************************************* + +.. _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 0000000..355c3ef Binary files /dev/null and b/docs/crypto/architecture/component_overview.png differ 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..4504959 --- /dev/null +++ b/docs/crypto/architecture/design_decisions.rst @@ -0,0 +1,1015 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 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 + # ******************************************************************************* + +.. _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..8683ccb --- /dev/null +++ b/docs/crypto/architecture/dynamic_architecture.rst @@ -0,0 +1,299 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 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 + # ******************************************************************************* + +.. _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..18fe621 --- /dev/null +++ b/docs/crypto/architecture/index.rst @@ -0,0 +1,195 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 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 + # ******************************************************************************* + +.. _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..395f973 --- /dev/null +++ b/docs/crypto/architecture/interfaces.rst @@ -0,0 +1,419 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 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 + # ******************************************************************************* + +.. _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..f9e78e3 --- /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 + + InitMac(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 + + InitMac(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_INITIALIZED .. + - 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..3eb22bd --- /dev/null +++ b/docs/crypto/architecture/key_management_details.rst @@ -0,0 +1,689 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 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 + # ******************************************************************************* + +.. _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 ``EVP_MAC`` API. + +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** (``OpenSslHmacHandler::InitializeContext``): + + - Validates ``m_algorithm == "HMAC-SHA256"``. + - Calls ``EVP_MAC_fetch(NULL, "HMAC", NULL)`` to obtain an ``EVP_MAC*`` + object, then ``EVP_MAC_CTX_new(m_mac)`` to allocate the ``EVP_MAC_CTX``. + - 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. + - Stores ``init_params``; the actual ``EVP_MAC_init(m_ctx, key_bytes, + key_len, params)`` call (with ``OSSL_PARAM`` selecting the digest) is + deferred to ``InitMac()`` so the context can be re-initialized on + ``MAC_INIT`` without re-fetching the MAC object. + +At the end of ``CTX_CREATE`` the daemon returns ``context_node_id`` to the +client. The key material is ready to be consumed by ``EVP_MAC_init``; +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_INITIALIZED`` on first update; ``STREAM_INITIALIZED → STREAM_ACTIVE`` + on subsequent updates) and calls ``MacHandlerImpl::UpdateMac(dataToMac)``. +3. The handler extracts raw bytes via ``ExtractBufferData`` then calls + ``EVP_MAC_update(m_ctx, data, len)`` to feed data into the running HMAC. + +``MAC_FINALIZE``: + +1. ``MacExecutor`` validates ``STREAM_INITIALIZED/ACTIVE → IDLE`` transition. +2. ``OpenSslHmacHandler::FinalizeMac`` → ``EVP_MAC_final(m_ctx, output, + &hmac_len, buf_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 ``OpenSslHmacHandler::~OpenSslHmacHandler`` → + ``EVP_MAC_CTX_free`` + ``EVP_MAC_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..fc51ec6 --- /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 : EVP_MAC_fetch(NULL, "HMAC", NULL) → m_mac\nEVP_MAC_CTX_new(m_mac) → m_ctx +Mac -> Mac : downcast bound_key_handler\n→ OpenSslKeyHandler\n(type-safe: check ProviderId == 0) +Mac -> Mac : GetRawKeyBytes(key_len)\n→ key_bytes pointer +Mac -> Mac : store init_params for lazy EVP_MAC_init\n(called at MAC_INIT / first use) +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_INITIALIZED on first MAC_UPDATE +Exec -> Mac : UpdateMac(data_ptr, data_len) +Mac -> Mac : EVP_MAC_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_FINALIZE)\nparam[context_id], param[output_buf] +activate Med +Med -> Mac : Execute(MAC_FINALIZE, params) +activate Mac +Mac -> Exec : Execute(handler=*this, MAC_FINALIZE, params) +note right : ValidateStreamTransition:\nSTREAM_INITIALIZED/ACTIVE → IDLE +Exec -> Mac : FinalizeMac(output_buf) +Mac -> Mac : EVP_MAC_final(m_ctx, output, &hmac_len, buf_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..373b6fa --- /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 { + #InitHash(...) : Result + #UpdateHash(...) : Result + #FinalizeHash(...) : Result + -m_executor : HashExecutor + } + ScoreHashHandler .up.|> IHandler + + abstract class ScoreMacHandler { + #InitMac(...) : Result + #UpdateMac(...) : Result + #FinalizeMac(...) : 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..17d4c3a --- /dev/null +++ b/docs/crypto/architecture/provider_architecture.rst @@ -0,0 +1,108 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 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 + # ******************************************************************************* + +.. _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_FINALIZE``). +- ``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 (``InitHash``, ``UpdateHash``, +``FinalizeHash``). 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_FINALIZE``, +``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..14f8f28 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,6 @@ .. # ******************************************************************************* - # Copyright (c) 2024 Contributors to the Eclipse Foundation + # Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -12,10 +12,15 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -Module Template Documentation -============================= +Crypto Documentation +==================== -This documentation describes the structure, usage and configuration of the Bazel-based C++/Rust module template. +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 +29,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 +69,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..2852f40 100644 --- a/examples/BUILD +++ b/examples/BUILD @@ -1,8 +1,45 @@ -# Needed for Dash tool to check python dependency licenses. -filegroup( - name = "cargo_lock", - srcs = [ - "Cargo.lock", +# ******************************************************************************* +# Copyright (c) 2026 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_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..3cbbced --- /dev/null +++ b/examples/hashing_example.cpp @@ -0,0 +1,179 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +/// @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..ab74132 --- /dev/null +++ b/examples/memory_allocator_example.cpp @@ -0,0 +1,145 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +/// @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..c5715ec --- /dev/null +++ b/platforms/BUILD @@ -0,0 +1,61 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* +# 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..e211935 100644 --- a/project_config.bzl +++ b/project_config.bzl @@ -1,5 +1,17 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* # project_config.bzl PROJECT_CONFIG = { "asil_level": "QM", - "source_code": ["rust"], + "source_code": ["cpp", "rust", "python"], } diff --git a/pyproject.toml b/pyproject.toml index 5819ca5..c7d5fac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,16 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* + [tool.basedpyright] extends = "bazel-bin/ide_support.runfiles/score_tooling+/python_basics/pyproject.toml" verboseOutput = true diff --git a/score/crypto/api/BUILD b/score/crypto/api/BUILD new file mode 100644 index 0000000..0f168e2 --- /dev/null +++ b/score/crypto/api/BUILD @@ -0,0 +1,28 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 = "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..bf8f42b --- /dev/null +++ b/score/crypto/api/control_plane/BUILD @@ -0,0 +1,49 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 = "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", + "@score_baselibs//score/mw/log", + ], +) 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..0b3b074 --- /dev/null +++ b/score/crypto/api/control_plane/connection_factory.hpp @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..e9e3304 --- /dev/null +++ b/score/crypto/api/control_plane/i_connection.hpp @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..38db463 --- /dev/null +++ b/score/crypto/api/control_plane/src/connection_factory.cpp @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/mw/log/logging.h" + +#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) + { + score::mw::log::LogError() + << "[CONTROL_CLIENT_NODE_FACTORY] ERROR - Unsupported endpoint protocol. Only unix domain sockets " + "are supported."; + 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) + { + score::mw::log::LogError() << "[CONTROL_CLIENT_NODE_FACTORY] ERROR - Failed to create connection to socket: " + << socketPath; + 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..3893326 --- /dev/null +++ b/score/crypto/api/control_plane/src/connection_impl.cpp @@ -0,0 +1,86 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/mw/log/logging.h" + +#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()) + { + score::mw::log::LogError() << "[IPC][ConnectionImpl] ERROR: Failed to build CONNECTION_CLOSE request"; + return; + } + + auto responseRes = mClient->SendRequest(requestRes.value()); + + auto validator = proto::ControlResponseValidator::FromResult(responseRes); + validator.expectOperation(ctrl_ops::CloseConnection()).expectSuccess(); + + if (!validator.isValid()) + { + score::mw::log::LogError() << "[IPC][ConnectionImpl] ERROR: CONNECTION_CLOSE response validation failed: " + << validator.getError(); + 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..da3b6b6 --- /dev/null +++ b/score/crypto/api/control_plane/src/connection_impl.hpp @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..884c89c --- /dev/null +++ b/score/crypto/common/BUILD @@ -0,0 +1,32 @@ +# ******************************************************************************* +# Copyright (c) 2026 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: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..951cedc --- /dev/null +++ b/score/crypto/common/types.hpp @@ -0,0 +1,165 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..615a048 --- /dev/null +++ b/score/crypto/daemon/BUILD @@ -0,0 +1,59 @@ +# ******************************************************************************* +# Copyright (c) 2026 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_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..5e89b79 --- /dev/null +++ b/score/crypto/daemon/common/BUILD @@ -0,0 +1,51 @@ +# ******************************************************************************* +# Copyright (c) 2026 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: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", + "@score_baselibs//score/mw/log", + ], +) diff --git a/score/crypto/daemon/common/actors.hpp b/score/crypto/daemon/common/actors.hpp new file mode 100644 index 0000000..ebf6c55 --- /dev/null +++ b/score/crypto/daemon/common/actors.hpp @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..c1c278f --- /dev/null +++ b/score/crypto/daemon/common/algorithm_info.hpp @@ -0,0 +1,127 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..05a52f9 --- /dev/null +++ b/score/crypto/daemon/common/daemon_error.hpp @@ -0,0 +1,285 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..c5f8ba0 --- /dev/null +++ b/score/crypto/daemon/common/operation_names.hpp @@ -0,0 +1,226 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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: +/// score::mw::log::LogDebug() << "action=" << opId.operationAction ; +/// +/// // Write: +/// score::mw::log::LogDebug() << common::OpId{opId} ; +/// // Output: "HASH_HANDLER::HASH_FINALIZE [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 "score/mw/log/logging.h" +#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_FINALIZE: + return "HASH_FINALIZE"; + 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_FINALIZE: + return "MAC_FINALIZE"; + 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 +/// score::mw::log::LogDebug() << "[MED] dispatching" << common::OpId{opId} ; +/// // → "[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 << "]"; +} + +inline score::mw::log::LogStream& operator<<(score::mw::log::LogStream& os, const OpId& op) +{ + return os << ActorName(op.id.operationActor) << "::" << ActionName(op.id.operationActor, op.id.operationAction) + << " [actor=" << static_cast(op.id.operationActor) + << ", action=" << static_cast(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..361c50b --- /dev/null +++ b/score/crypto/daemon/common/secure_memory.hpp @@ -0,0 +1,76 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..6a7ba43 --- /dev/null +++ b/score/crypto/daemon/common/types.hpp @@ -0,0 +1,176 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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_INITIALIZED = 1, // Init operation completed, stream ready for Update + STREAM_ACTIVE = 2, // Stream active, accepting Update or Finalize +}; + +/** + * @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..c9c9f85 --- /dev/null +++ b/score/crypto/daemon/config/BUILD @@ -0,0 +1,78 @@ +# ******************************************************************************* +# Copyright (c) 2026 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") + +# 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", + "@score_baselibs//score/mw/log", + ], +) + +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..a5f0956 --- /dev/null +++ b/score/crypto/daemon/config/crypto_config.fbs @@ -0,0 +1,67 @@ +// ******************************************************************************* +// Copyright (c) 2026 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 +// ******************************************************************************* + +// 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..5069154 --- /dev/null +++ b/score/crypto/daemon/config/inc/config.hpp @@ -0,0 +1,472 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..6bf4aa9 --- /dev/null +++ b/score/crypto/daemon/config/src/config.cpp @@ -0,0 +1,136 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/crypto/daemon/config/inc/config.hpp" +#include "score/crypto/daemon/config/src/flatbuffer_config_parser.hpp" + +#include "score/mw/log/logging.h" +#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)) + { + score::mw::log::LogError() << "[CONFIG] Configuration file does not exist:" << config_file_path; + return false; + } + score::mw::log::LogDebug() << "[CONFIG] Parsing configuration from:" << config_file_path; + auto result = FlatBufferConfigParser::ParseFromFile(config_file_path, m_key); + if (!result.has_value()) + { + score::mw::log::LogError() << "[CONFIG] Failed to parse FlatBuffers configuration file: " + << config_file_path; + return false; + } + return true; + } + + // Try default paths (in order of preference) + for (const auto& path : DEFAULT_CONFIG_PATHS) + { + if (std::filesystem::exists(path)) + { + score::mw::log::LogDebug() << "[CONFIG] Found configuration at default path:" << path; + auto result = FlatBufferConfigParser::ParseFromFile(path, m_key); + if (!result.has_value()) + { + score::mw::log::LogError() << "[CONFIG] Failed to parse configuration from:" << path; + return false; + } + return true; + } + } + + score::mw::log::LogError() << "[CONFIG] ERROR: No configuration file found in default paths:"; + for (const auto& path : DEFAULT_CONFIG_PATHS) + { + score::mw::log::LogError() << " -" << path; + } + 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..33d8945 --- /dev/null +++ b/score/crypto/daemon/config/src/flatbuffer_config_parser.cpp @@ -0,0 +1,292 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/mw/log/logging.h" +#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) + { + score::mw::log::LogError() << LOG_PREFIX + << "Buffer too small or null pointer. Required: >=4 bytes, Got:" << size; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + if (!CryptoConfigBufferHasIdentifier(data)) + { + score::mw::log::LogError() << LOG_PREFIX << "Buffer does not have expected file identifier"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + + return std::monostate{}; +} + +Expected FlatBufferConfigParser::ParseKeySlotConfig(const CryptoConfig* root, + KeyConfig& out_config) +{ + if (!root) + { + score::mw::log::LogError() << LOG_PREFIX << "Failed to get FlatBuffers root object"; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "Failed to get key_slot_config from root object"; + 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()); + } + + score::mw::log::LogDebug() << LOG_PREFIX << "Successfully parsed configuration. Loaded " + << out_config.GetSlotEntries().size() << " slot(s) and " + << out_config.GetAppResourceEntries().size() << " app resource mapping(s)."; + + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "Null slot entry encountered - invalid configuration"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + + KeyConfig::KeySlotEntry slot_entry; + + if (!entry->slot_name()) + { + score::mw::log::LogError() << LOG_PREFIX << "Slot entry missing required field 'slot_name'"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + slot_entry.slot_name = entry->slot_name()->str(); + + if (!entry->algorithm()) + { + score::mw::log::LogError() << LOG_PREFIX << "Slot entry missing required field 'algorithm'"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + slot_entry.algorithm = entry->algorithm()->str(); + + if (!entry->provider_names()) + { + score::mw::log::LogError() << LOG_PREFIX << "Slot entry missing required field 'provider_names'"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "Slot entry missing required field 'allowed_operations'"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + slot_entry.allowed_operations = entry->allowed_operations()->str(); + + if (!entry->allowed_uids()) + { + score::mw::log::LogError() << LOG_PREFIX << "Slot entry missing required field 'allowed_uids'"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + for (auto uid : *entry->allowed_uids()) + { + slot_entry.allowed_uids.push_back(uid); + } + + if (!entry->allowed_write_uids()) + { + score::mw::log::LogError() << LOG_PREFIX << "Slot entry missing required field 'allowed_write_uids'"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "Slot entry missing required field 'deployment_path'"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + slot_entry.deployment_path = entry->deployment_path()->str(); + + if (!entry->deployment_format()) + { + score::mw::log::LogError() << LOG_PREFIX << "Slot entry missing required field 'deployment_format'"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + slot_entry.deployment_format = entry->deployment_format()->str(); + + out_config.AddSlotEntry(std::move(slot_entry)); + + score::mw::log::LogDebug() << LOG_PREFIX << "Loaded slot: '" << out_config.GetSlotEntries().back().slot_name + << "'"; + } + + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "Failed to open file:" << filepath; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + // Get file size + std::streamsize size = file.tellg(); + if (size <= 0) + { + score::mw::log::LogError() << LOG_PREFIX << "File is empty:" << filepath; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + file.seekg(0, std::ios::beg); + + // Read entire file into buffer + if (size > std::numeric_limits::max()) + { + score::mw::log::LogError() << LOG_PREFIX << "File size exceeds maximum allowed size:" << filepath; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + std::vector buffer(static_cast(size)); + if (!file.read(reinterpret_cast(buffer.data()), size)) + { + score::mw::log::LogError() << LOG_PREFIX << "Failed to read file:" << filepath; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "Null app resource entry encountered - invalid configuration"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "App resource entry missing required field 'app_resource_id'"; + return make_unexpected(common::DaemonErrorCode::kInternalError); + } + resource_entry.app_resource_id = entry->app_resource_id()->str(); + + if (!entry->slot_name()) + { + score::mw::log::LogError() << LOG_PREFIX << "App resource entry missing required field 'slot_name'"; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "Invalid buffer: null pointer or zero size"; + return make_unexpected(common::DaemonErrorCode::kInvalidArgument); + } + + auto validateRes = ValidateBuffer(data, size); + if (!validateRes.has_value()) + { + score::mw::log::LogError() << LOG_PREFIX << "Buffer validation failed"; + return make_unexpected(validateRes.error()); + } + + // Get root FlatBuffers object (CryptoConfig is the root_type) + const auto* root = flatbuffers::GetRoot(data); + if (!root) + { + score::mw::log::LogError() << LOG_PREFIX << "Failed to get FlatBuffers root object from buffer"; + 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..3fca432 --- /dev/null +++ b/score/crypto/daemon/config/src/flatbuffer_config_parser.hpp @@ -0,0 +1,144 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..663da6c --- /dev/null +++ b/score/crypto/daemon/control_plane/BUILD @@ -0,0 +1,65 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 = "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/mw/log", + "@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/mw/log", + "@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..ed57dfc --- /dev/null +++ b/score/crypto/daemon/control_plane/basic_handler_chain_factory.hpp @@ -0,0 +1,77 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..3ad9280 --- /dev/null +++ b/score/crypto/daemon/control_plane/control_operations.h @@ -0,0 +1,66 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..4763eee --- /dev/null +++ b/score/crypto/daemon/control_plane/control_protocol.h @@ -0,0 +1,867 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#ifndef SCORE_CRYPTO_DAEMON_CONTROL_PLANE_CONTROL_PROTOCOL_H +#define SCORE_CRYPTO_DAEMON_CONTROL_PLANE_CONTROL_PROTOCOL_H + +#include "score/mw/log/logging.h" +#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) + { + score::mw::log::LogError() + << "[CONTROL_OP_REQ_BUILDER] ERROR - Cannot build OperationRequest due to previous errors in " + "building process"; + + 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()) + { + score::mw::log::LogError() + << "[CONTROL_OP_REQ_BUILDER] ERROR - Trying to add parameter without an operation"; + 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) + { + score::mw::log::LogError() + << "[CONTROL_OP_RESP_BUILDER] ERROR - Cannot build OperationResponse due to previous errors in " + "building process"; + + 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()) + { + score::mw::log::LogError() + << "[CONTROL_OP_RESP_BUILDER] ERROR - Trying to add parameter without an operation"; + 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) + { + score::mw::log::LogError() << "[ControlResponseValidator] ERROR - " << m_errorMsg; + } + } +}; + +// ============================================================================ +// 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..769976e --- /dev/null +++ b/score/crypto/daemon/control_plane/i_control_server.h @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..e1f86f5 --- /dev/null +++ b/score/crypto/daemon/control_plane/i_handler_chain_factory.hpp @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..57c0bad --- /dev/null +++ b/score/crypto/daemon/control_plane/i_request_handler.hpp @@ -0,0 +1,68 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..196e8de --- /dev/null +++ b/score/crypto/daemon/control_plane/src/basic_handler_chain_factory.cpp @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..7ff9e42 --- /dev/null +++ b/score/crypto/daemon/control_plane/src/connection_handler.cpp @@ -0,0 +1,171 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +#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) +{ + score::mw::log::LogDebug() << "[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; + std::ostringstream tid; + tid << std::this_thread::get_id(); + score::mw::log::LogDebug() << "[CONTROL_HANDLER] Thread ID:" << tid.str(); + // Validate empty request + if (request.operation.operations.empty()) + { + score::mw::log::LogDebug() << "[CONTROL_HANDLER] Empty operation, returning error"; + 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; + score::mw::log::LogDebug() << "[CONTROL_HANDLER] Operation actor:" << opId.operationActor + << " action:" << opId.operationAction; + 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)) + { + score::mw::log::LogError() + << "[CONTROL_HANDLER] Failed to process control operation: actor=" << opId.operationActor + << " action=" << opId.operationAction; + score::mw::log::LogError() + << "[CONTROL_HANDLER] Stopping further processing of operations in this request"; + 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) +{ + score::mw::log::LogDebug() << "[CONTROL_HANDLER] Processing control operation: actor=" << opId.operationActor + << " action=" << opId.operationAction; + + 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()) + { + score::mw::log::LogError() << "[CONTROL_HANDLER] Failed to create connection"; + responseBuilder.operation(opId).return_error(dataNodeIdRes.error()); + return false; + } + + auto connection_id = dataNodeIdRes.value(); + + score::mw::log::LogDebug() << "[CONTROL_HANDLER] Created connection with connection_id:" << connection_id; + + // 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; + + score::mw::log::LogDebug() << "[CONTROL_HANDLER] Closing client_id:" << client_id + << " connection_id:" << connection_id; + + // 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()) + { + score::mw::log::LogDebug() << "[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) +{ + score::mw::log::LogDebug() << "[CONTROL_HANDLER] Received unknown operationAction=" << opId.operationAction; + 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..a096f93 --- /dev/null +++ b/score/crypto/daemon/control_plane/src/connection_handler.hpp @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..4b029b6 --- /dev/null +++ b/score/crypto/daemon/data_manager/BUILD @@ -0,0 +1,37 @@ +# ******************************************************************************* +# Copyright (c) 2026 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: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", + "@score_baselibs//score/mw/log", + ], +) 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..88a3133 --- /dev/null +++ b/score/crypto/daemon/data_manager/data_manager.hpp @@ -0,0 +1,204 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..0238170 --- /dev/null +++ b/score/crypto/daemon/data_manager/data_node.hpp @@ -0,0 +1,227 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..585e79f --- /dev/null +++ b/score/crypto/daemon/data_manager/data_node_accessor.hpp @@ -0,0 +1,218 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#ifndef CRYPTO_DAEMON_DATA_MANAGER_DATA_NODE_ACCESSOR_HPP_ +#define CRYPTO_DAEMON_DATA_MANAGER_DATA_NODE_ACCESSOR_HPP_ + +#include "score/mw/log/logging.h" + +#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()) + { + score::mw::log::LogError() << LOG_PREFIX << "Failed to release node (" << m_node->getClientId() << ", " + << m_node->getNodeId() << ") in destructor"; + } + } + } + + /// @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()) + { + score::mw::log::LogError() << LOG_PREFIX << "Failed to release node (" << m_node->getClientId() << ", " + << m_node->getNodeId() << ") in move assignment operator"; + } + } + + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "Could not convert to derived type"; + 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..58c904c --- /dev/null +++ b/score/crypto/daemon/data_manager/docs/index.rst @@ -0,0 +1,137 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 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 + # ******************************************************************************* +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..420af73 --- /dev/null +++ b/score/crypto/daemon/data_manager/i_data_manager.hpp @@ -0,0 +1,190 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..0b15376 --- /dev/null +++ b/score/crypto/daemon/data_manager/src/data_manager.cpp @@ -0,0 +1,524 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/mw/log/logging.h" +#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) +{ + score::mw::log::LogDebug() << LOG_PREFIX << "addNode for clientId:" << clientId; + + if (!node) + { + score::mw::log::LogError() << LOG_PREFIX << "addNode: Invalid node sptr"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + const std::lock_guard lock(m_mutex); + + auto nodeIdRes = getNextNodeIdLocked(clientId, lock); + if (!nodeIdRes) + { + score::mw::log::LogError() << LOG_PREFIX << "addNode: Could not get a nodeId"; + 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); + + score::mw::log::LogDebug() << LOG_PREFIX << "addNode new nodeId:" << nodeId; + + return nodeId; +} + +Expected +DataManager::addChildNode(ClientId clientId, DataNodeId parentId, std::shared_ptr node) +{ + score::mw::log::LogDebug() << LOG_PREFIX << "addChildNode for clientId:" << clientId + << " with parentId:" << parentId; + + if (!node) + { + score::mw::log::LogError() << LOG_PREFIX << "addChildNode: Invalid node sptr"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "addChildNode: Failed to get parent"; + return make_unexpected(parentRes.error()); + } + const auto& parent = parentRes.value(); + + auto nodeIdRes = getNextNodeIdLocked(clientId, lock); + if (!nodeIdRes) + { + score::mw::log::LogError() << LOG_PREFIX << "addChildNode: Could not get a nodeId"; + 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); + + score::mw::log::LogDebug() << LOG_PREFIX << "addChildNode new nodeId:" << nodeId; + + return nodeId; +} + +Expected +DataManager::addSiblingNode(ClientId clientId, DataNodeId siblingId, std::shared_ptr node) +{ + score::mw::log::LogDebug() << LOG_PREFIX << "addSiblingNode for clientId:" << clientId + << " with siblingId:" << siblingId; + + if (!node) + { + score::mw::log::LogError() << LOG_PREFIX << "addSiblingNode: Invalid node sptr"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "addSiblingNode: Failed to get sibling"; + return make_unexpected(siblingRes.error()); + } + const auto& sibling = siblingRes.value(); + + auto parentIdRes = sibling->getParent(DataNodeManagerToken{}); + if (!parentIdRes.has_value()) + { + score::mw::log::LogError() << LOG_PREFIX << "addSiblingNode: Sibling has no parent"; + return make_unexpected(parentIdRes.error()); + } + const auto parentId = parentIdRes.value(); + + auto parentRes = getNodeLocked(clientId, parentId, lock); + if (!parentRes.has_value()) + { + score::mw::log::LogError() << LOG_PREFIX << "addSiblingNode: Failed to get parent"; + return make_unexpected(parentRes.error()); + } + const auto& parent = parentRes.value(); + + auto nodeIdRes = getNextNodeIdLocked(clientId, lock); + if (!nodeIdRes) + { + score::mw::log::LogError() << LOG_PREFIX << "addSiblingNode: Could not get a nodeId"; + 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); + + score::mw::log::LogDebug() << LOG_PREFIX << "addSiblingNode new nodeId:" << nodeId; + + return nodeId; +} + +Expected DataManager::deleteClientNodes( + ClientId clientId) +{ + score::mw::log::LogDebug() << LOG_PREFIX << "deleteClientNodes for clientId:" << clientId; + + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "deleteClientNodes: Unknown clientId:" << clientId; + 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) +{ + score::mw::log::LogDebug() << LOG_PREFIX << "deleteNode for clientId:" << clientId << " nodeId:" << nodeId; + + 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) +{ + score::mw::log::LogDebug() << LOG_PREFIX << "removeNodesFromStorage with clientId:" << clientId + << " nodeId:" << rootNodeId; + + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "removeNodesFromStorage: getNodeLocked failed for node " + << currentId; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX + << "removeNodesFromStorage: removeSingleNodeFromStorage failed for node " + << currentId; + result = make_unexpected(subResult.error()); + } + } + + return result; +} + +Expected DataManager::removeSingleNodeFromStorage( + const std::shared_ptr& node, + ClientId clientId, + DataNodeId nodeId, + const std::lock_guard& _lock) +{ + score::mw::log::LogDebug() << LOG_PREFIX << "removeSingleNodeFromStorage with clientId:" << clientId + << " nodeId:" << nodeId; + + 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 + { + score::mw::log::LogError() << LOG_PREFIX << "removeNodesFromStorage: getNodeLocked of parent failed " + << parentId.value(); + 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 + { + score::mw::log::LogError() << LOG_PREFIX << "removeSingleNodeFromStorage: Not found: clientId:" << clientId; + 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*/) +{ + score::mw::log::LogDebug() << LOG_PREFIX << "removeEntryNodeFromIndex: ClientId:" << clientId + << " nodeId:" << nodeId; + auto clientItr = m_entry_nodes_per_client.find(clientId); + if (clientItr == m_entry_nodes_per_client.end()) + { + score::mw::log::LogError() << LOG_PREFIX << "removeEntryNodeFromIndex: Not found: clientId:" << clientId; + 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 + { + score::mw::log::LogError() << LOG_PREFIX << "removeEntryNodeFromIndex: Not found: nodeId:" << nodeId; + 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 +{ + score::mw::log::LogDebug() << LOG_PREFIX << "getNodeLocked: ClientId:" << clientId << " nodeId:" << nodeId; + + auto clientItr = m_node_map.find(clientId); + if (clientItr == m_node_map.end()) + { + score::mw::log::LogError() << LOG_PREFIX << "getNodeLocked: Not found: clientId:" << clientId; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + const auto& client = clientItr->second; + + auto node = client.find(nodeId); + if (node == client.end()) + { + score::mw::log::LogError() << LOG_PREFIX << "getNodeLocked: Not found: nodeId:" << nodeId; + 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--; + } + + score::mw::log::LogError() << LOG_PREFIX << "getNextNodeIdLocked: Not free nodeId found"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kQuotaExceeded); +} + +Expected DataManager::releaseNodeAccessor( + ClientId clientId, + DataNodeId nodeId) const +{ + score::mw::log::LogDebug() << LOG_PREFIX << "releaseAccessor for clientId:" << clientId + << " with nodeId:" << nodeId; + + const std::lock_guard lock(m_mutex); + + auto clientItr = m_busy_nodes.find(clientId); + if (clientItr == m_busy_nodes.end()) + { + score::mw::log::LogError() << LOG_PREFIX << "releaseAccessor: Failed to get client"; + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + auto& client = clientItr->second; + + auto nodeItr = client.find(nodeId); + if (nodeItr == client.end()) + { + score::mw::log::LogError() << LOG_PREFIX << "releaseAccessor: Failed to get node"; + 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 +{ + score::mw::log::LogDebug() << LOG_PREFIX << "getNodeAccessor for clientId:" << clientId + << " with nodeId:" << nodeId; + + const std::lock_guard lock(m_mutex); + + auto nodeRes = getNodeLocked(clientId, nodeId, lock); + if (!nodeRes.has_value()) + { + score::mw::log::LogError() << LOG_PREFIX << "getNodeAccessor: Failed to get node"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "getNodeAccessor: Node is currently busy"; + 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..c521d43 --- /dev/null +++ b/score/crypto/daemon/data_manager/src/data_node.cpp @@ -0,0 +1,118 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/mw/log/logging.h" +#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 + { + score::mw::log::LogError() << LOG_PREFIX << "removeChild could not find child with nodeId:" << nodeId; + 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..1d60879 --- /dev/null +++ b/score/crypto/daemon/data_manager/src/data_node_manager_token.hpp @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..1bc2275 --- /dev/null +++ b/score/crypto/daemon/key_management/BUILD @@ -0,0 +1,118 @@ +# ******************************************************************************* +# Copyright (c) 2026 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: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..f850dd2 --- /dev/null +++ b/score/crypto/daemon/key_management/core/key_entry.hpp @@ -0,0 +1,128 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..d0c56e9 --- /dev/null +++ b/score/crypto/daemon/key_management/core/key_management_service.cpp @@ -0,0 +1,603 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +#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) + { + score::mw::log::LogError() << LOG_PREFIX << "constructor: null dependency injected"; + } +} + +// --------------------------------------------------------------------------- +// RegisterKeyMaterial +// --------------------------------------------------------------------------- + +Expected KeyManagementService::RegisterKeyMaterial( + const KeyRegistrationParams& params, + IKeyHandler::Sptr handler) +{ + if (!handler) + { + score::mw::log::LogError() << LOG_PREFIX << "RegisterKeyMaterial: null handler"; + 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) + { + score::mw::log::LogError() + << LOG_PREFIX << "RegisterKeyMaterial: race condition - slot registered but ID not found"; + 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) + { + score::mw::log::LogError() + << LOG_PREFIX << "RegisterKeyMaterial: existing key not found after race condition"; + 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); + } + + score::mw::log::LogError() << LOG_PREFIX << "LoadOrShare: LoadKey failed and slot not in registry"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "ReleaseKeyMaterial: deleteNode failed"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "CreateKeyDataNode: addChildNode failed"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "ResolveKeySlot: SlotRegistry::ResolveAppResource failed for '" + << resource_name << "'"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "ResolveKeySlot: addNode failed"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX + << "BindKeyToContext: node lookup failed for key_node_id=" << key_node_id; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "BindKeyToContext: kKeySlot downCast failed (internal)"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX + << "BindKeyToContext: KeySlotDataNode has no config " + "(key_node_id=" + << key_node_id << ")"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "BindKeyToContext: no primary provider for slot " + << key_node_id; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "BindKeyToContext: cross-provider binding not supported " + << "(key provider=" << slot_provider_id + << ", target provider=" << target_provider_id << ")"; + return score::crypto::make_unexpected( + score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); + } + + auto slot_provider = m_provider_manager->GetProvider(slot_provider_id); + if (!slot_provider) + { + score::mw::log::LogError() << LOG_PREFIX << "BindKeyToContext: provider '" << slot_provider_id + << "' not found"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidArgument); + } + + auto slot_handler = slot_provider->GetKeySlotHandler(*slot_config); + if (!slot_handler) + { + score::mw::log::LogError() << LOG_PREFIX << "BindKeyToContext: provider '" << slot_provider_id + << "' does not support slot operations"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "BindKeyToContext: LoadOrShare failed for" << key_node_id; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "BindKeyToContext: kKeyData downCast failed (internal)"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInternalError); + } + + auto key_data_node = ref_acc.value()->GetKeyEntry(); + if (!key_data_node) + { + score::mw::log::LogError() << LOG_PREFIX + << "BindKeyToContext: KeyDataNode has no backing " + "KeyDataNode"; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "BindKeyToContext: cross-provider binding not supported " + << "(key provider=" << key_provider_id + << ", target provider=" << target_provider_id << ")"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX + << "BindKeyToContext: failed to create context-owned " + "KeyDataNode for loaded key"; + 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. + score::mw::log::LogError() << LOG_PREFIX << "BindKeyToContext: key_node_id=" << key_node_id + << " is neither a KeySlotDataNode nor a KeyDataNode" + << " (node_type=" << static_cast(node_type) << ")"; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "ResolveTargetProvider: no provider for type " + << static_cast(requested_type); + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "ResolveTargetProvider: node lookup failed for key_node_id=" + << key_node_id.value(); + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "ResolveTargetProvider: slot has no config or providers"; + 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; + } + } + + score::mw::log::LogError() << LOG_PREFIX << "ResolveTargetProvider: no compatible provider for slot '" + << config->slot_name << "' with type" << static_cast(requested_type); + 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) + { + score::mw::log::LogError() << LOG_PREFIX + << "ResolveTargetProvider: KeyDataNode has no " + "backing KeyDataNode"; + 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; + } + + score::mw::log::LogError() << LOG_PREFIX << "ResolveTargetProvider: key's provider '" << key_provider_id + << "' incompatible with type" << static_cast(requested_type); + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kIncompatibleKeyType); + } + + score::mw::log::LogError() << LOG_PREFIX << "ResolveTargetProvider: key_node_id=" << key_node_id.value() + << " is neither a KeySlotDataNode nor a KeyDataNode"; + 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..466a1bb --- /dev/null +++ b/score/crypto/daemon/key_management/core/key_management_service.hpp @@ -0,0 +1,263 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..aa29ee6 --- /dev/null +++ b/score/crypto/daemon/key_management/core/key_registry.cpp @@ -0,0 +1,189 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/crypto/daemon/key_management/core/key_registry.hpp" + +#include "score/crypto/daemon/key_management/core/key_entry.hpp" + +#include "score/mw/log/logging.h" +#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) + { + score::mw::log::LogError() << LOG_PREFIX << "RegisterSlotKey: slot" << slot_handle.index + << " already registered"; + 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; + } + } + + score::mw::log::LogDebug() << LOG_PREFIX << "CleanupClient: removing key" << id + << " (ref_count reached 0 after client" << client_id << " cleanup)"; + 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..246f3a0 --- /dev/null +++ b/score/crypto/daemon/key_management/core/key_registry.hpp @@ -0,0 +1,141 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..e70d7ce --- /dev/null +++ b/score/crypto/daemon/key_management/detail/slot_info_builder.hpp @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..e56ff3d --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/i_key_factory.cpp @@ -0,0 +1,135 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..631e104 --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/i_key_factory.hpp @@ -0,0 +1,157 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..d189ec4 --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/i_key_handler.hpp @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..dcbaf30 --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/i_key_slot_catalog.hpp @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..6e5d9b8 --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/i_key_slot_handler.cpp @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..4cf1dba --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/i_key_slot_handler.hpp @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..d602347 --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/key_management_operations.hpp @@ -0,0 +1,83 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..d86f4be --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/key_slot_config.hpp @@ -0,0 +1,220 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..f06e2e6 --- /dev/null +++ b/score/crypto/daemon/key_management/interfaces/key_types.hpp @@ -0,0 +1,184 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..5d35d50 --- /dev/null +++ b/score/crypto/daemon/key_management/key_management_module.cpp @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +#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); + + score::mw::log::LogDebug() << LOG_PREFIX << "Key management module initialized from config. Slots: " + << module->m_slot_registry->GetSlotCount(); + + 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..537a03f --- /dev/null +++ b/score/crypto/daemon/key_management/key_management_module.hpp @@ -0,0 +1,89 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..c219cfe --- /dev/null +++ b/score/crypto/daemon/key_management/nodes/key_data_node.hpp @@ -0,0 +1,111 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..281da87 --- /dev/null +++ b/score/crypto/daemon/key_management/nodes/key_slot_data_node.hpp @@ -0,0 +1,101 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..ee5f5c7 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/access_policy_enforcer.cpp @@ -0,0 +1,109 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..9f7db55 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/access_policy_enforcer.hpp @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..56eb4c9 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/config_driven_slot_catalog.cpp @@ -0,0 +1,133 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +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)); + + score::mw::log::LogDebug() << LOG_PREFIX << "Registered slot '" << entry.slot_name << "' (primary_provider=" + << (entry.provider_names.empty() ? "" : entry.provider_names[0]) + << ", algorithm=" << entry.algorithm << ")"; + } + + score::mw::log::LogDebug() << LOG_PREFIX << "Loaded" << entries.size() << " slot(s) from configuration."; + + // 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..061c92b --- /dev/null +++ b/score/crypto/daemon/key_management/slot/config_driven_slot_catalog.hpp @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..ba4f09e --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/BUILD @@ -0,0 +1,49 @@ +# ******************************************************************************* +# Copyright (c) 2026 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: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..db5d53a --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/deployment_path_utils.hpp @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..fc19c84 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/i_deployment_loader.hpp @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..7cad5f4 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/i_deployment_writer.hpp @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..1a85eb6 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.cpp @@ -0,0 +1,120 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.hpp" + +#include "score/mw/log/logging.h" +#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()) + { + score::mw::log::LogError() << kLogPrefix << "Cannot open deployment descriptor:" << path; + 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..6d61862 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_loader.hpp @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..81cc7a2 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.cpp @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.hpp" + +#include "score/mw/log/logging.h" +#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()) + { + score::mw::log::LogError() << kLogPrefix << "Cannot open deployment descriptor for writing:" << path; + 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()) + { + score::mw::log::LogError() << kLogPrefix << "Write error for deployment descriptor:" << path; + 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..b1be9bd --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment/kv/kv_deployment_writer.hpp @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..1786719 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment_loader.cpp @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +#include + +namespace score::crypto::daemon::key_management +{ + +score::crypto::Expected DeploymentLoader::Load( + const std::string& path, + const std::string& format) +{ + if (!IsDeploymentPathSafe(path)) + { + score::mw::log::LogError() << LOG_PREFIX << "Unsafe deployment path rejected:" << path; + 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); } + + score::mw::log::LogError() << LOG_PREFIX << "Unsupported deployment format:" << format; + 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..f817316 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment_loader.hpp @@ -0,0 +1,70 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..01fa25d --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment_writer.cpp @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +#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)) + { + score::mw::log::LogError() << LOG_PREFIX << "Unsafe deployment path rejected:" << path; + 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); } + + score::mw::log::LogError() << LOG_PREFIX << "Unsupported deployment format:" << format; + 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..8b644c5 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/deployment_writer.hpp @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..7034d61 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/file_backed_slot_handler.cpp @@ -0,0 +1,133 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..defbaae --- /dev/null +++ b/score/crypto/daemon/key_management/slot/file_backed_slot_handler.hpp @@ -0,0 +1,88 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..5dffb47 --- /dev/null +++ b/score/crypto/daemon/key_management/slot/slot_registry.cpp @@ -0,0 +1,177 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" +#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()) + { + score::mw::log::LogError() << LOG_PREFIX << "Duplicate slot name ignored: '" << name << "'"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "Duplicate app resource mapping ignored: uid=" << uid + << " resource='" << app_resource_id << "'"; + 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 + { + score::mw::log::LogError() << LOG_PREFIX << "Warning: provider '" << name << "' not found for slot '" + << entry.config.slot_name << "'"; + } + } + } +} + +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..3eb576e --- /dev/null +++ b/score/crypto/daemon/key_management/slot/slot_registry.hpp @@ -0,0 +1,243 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..2d6bd2d --- /dev/null +++ b/score/crypto/daemon/mediator/BUILD @@ -0,0 +1,48 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 = "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..4e40233 --- /dev/null +++ b/score/crypto/daemon/mediator/i_mediator.hpp @@ -0,0 +1,85 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..7c8e119 --- /dev/null +++ b/score/crypto/daemon/mediator/mediator_operations.hpp @@ -0,0 +1,85 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..123833a --- /dev/null +++ b/score/crypto/daemon/mediator/src/mediator_impl.cpp @@ -0,0 +1,588 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/mw/log/logging.h" +#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) +{ + score::mw::log::LogDebug() << "[SCORE_API_MED] Processing request - " + << "RequestId:" << request.request_id << ", Client Id:" << request.client_id + << ", DataNodeId:" << request.data_node_id; + 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)) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR at operation index:" << idx; + break; + } + } + + // Build response + auto opResponse = responseBuilder.build().value_or(control_plane::protocol::OperationResponse()); + + ControlResponse response; + response.request_id = request.request_id; + response.operation = opResponse; + + score::mw::log::LogDebug() << "[SCORE_API_MED] Response operations:" << opResponse.operations.size(); + + 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) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Failed to handle mediator operation: " + << common::OpId{operation.operationId}; + } + return success; + } + + auto success = ForwardSingleOperation(request, operation, responseBuilder); + if (!success) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Failed to forward operation: " + << common::OpId{operation.operationId}; + } + 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) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Failed to handle context creation operation"; + } + + return success; + } + else if (operationIdentifier.operationAction == operations::CTX_CLOSE) + { + auto success = HandleContextCloseOperation(request, operation, responseBuilder); + if (!success) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Failed to handle context close operation"; + } + + return success; + } + else if (operationIdentifier.operationAction == operations::RESOLVE_RESOURCE) + { + auto success = + HandleResourceResolutionOperation(request.client_id, request.data_node_id, operation, responseBuilder); + if (!success) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Failed to handle resource resolution operation"; + } + return success; + } + + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Unknown mediator operation: " + << common::OpId{operationIdentifier}; + + 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()) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - No context found for context_id:" << context_id; + 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()) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Context node for context_id:" << context_id + << " is not a ContextDataNode"; + 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) + { + score::mw::log::LogError() + << "[SCORE_API_MED] ERROR - Context node accessor does not contain a handler for context_id: " + << context_id; + responseBuilder.operation(operationIdentifier).return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return false; + } + + score::mw::log::LogDebug() << "[SCORE_API_MED] Context found in data manager for context_id:" << context_id; + + // 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) +{ + score::mw::log::LogDebug() << "[SCORE_API_MED] Creating context node"; + + if (operation.parameters.size() < 2) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Not enough parameters for request"; + 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()) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Wrong parameter type for context_type"; + 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()) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Wrong parameter type for algorithm"; + 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()); + score::mw::log::LogDebug() << "[SCORE_API_MED] Requested ProviderType: wire=" + << static_cast(provider_type_res.value()) + << " resolved_daemon_type=" << static_cast(requested_provider_type); + } + } + + // 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()) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Provider resolution failed for keyed context" + << " (key_node_id=" << key_node_id << ")"; + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInvalidArgument); + return false; + } + score::mw::log::LogDebug() << "[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(); + provider = m_provider_manager->GetProvider(resolved_id_res.value()); + } + else + { + provider = m_provider_manager->GetProvider(requested_provider_type); + } + if (!provider) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - No providers available for type: " + << static_cast(requested_provider_type); + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return false; + } + score::mw::log::LogDebug() << "[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)"); + + auto crypto_ops = provider->GetCryptoHandlerFactory(); + if (crypto_ops == nullptr) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Crypto operations not available"; + 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()) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Handler or algorithm not supported:" << context_type + << "/" << algorithm; + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return false; + } + + auto handler = create_result.value(); + score::mw::log::LogDebug() << "[SCORE_API_MED] Created handler pointer:" << handler.get(); + + // --- 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()) + { + score::mw::log::LogError() << "[SCORE_API_MED] Adding Context to DataNodeManager failed"; + 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) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - key binding requires key management service"; + 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()) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - key binding failed for key_node_id=" << key_node_id; + 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()) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Handler initialization failed for context with error: " + << static_cast(init_result.error()); + m_data_manager->deleteNode(client_id, context_node_id); + responseBuilder.operation(operation.operationId) + .return_error(score::mw::crypto::CryptoErrorCode::kInternalError); + return false; + } + + score::mw::log::LogDebug() << "[SCORE_API_MED] Context node created for context_id:" << context_node_id; + + // 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 + score::mw::log::LogDebug() << "[SCORE_API_MED] Calling handler->Execute:" << common::OpId{exec_ctx.operationId}; + auto execute_result = handler->Execute(exec_ctx.operationId, exec_ctx.parameters); + + if (!execute_result.has_value()) + { + score::mw::log::LogError() << "[SCORE_API_MED] ERROR - Operation execution failed with error code: " + << static_cast(execute_result.error()); + responseBuilder.operation(exec_ctx.operationId).return_error(execute_result.error()); + return false; + } + + // Build response + score::mw::log::LogDebug() << "[SCORE_API_MED]" << common::OpId{exec_ctx.operationId} << " completed successfully"; + // 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; + + score::mw::log::LogDebug() << "[SCORE_API_MED] Closing context_id:" << context_id; + + // Remove context node from data manager + const bool removed = m_data_manager->deleteNode(client_id, context_id).has_value(); + + if (removed) + { + score::mw::log::LogDebug() << "[SCORE_API_MED] Context_id:" << context_id << " removed from data manager"; + responseBuilder.operation(operation.operationId).return_success(); + return true; + } + else + { + // Context not found - this is not necessarily an error + score::mw::log::LogDebug() << "[SCORE_API_MED] WARNING - Context_id:" << context_id + << " not found in data manager"; + 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()) + { + score::mw::log::LogError() << "[SCORE_API_MED] RESOLVE_RESOURCE: no resolver registered for ResourceType=" + << static_cast(key); + 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..6db9b6f --- /dev/null +++ b/score/crypto/daemon/mediator/src/mediator_impl.hpp @@ -0,0 +1,117 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..d210cd8 --- /dev/null +++ b/score/crypto/daemon/provider/BUILD @@ -0,0 +1,39 @@ +# ******************************************************************************* +# Copyright (c) 2026 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: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..9c0804c --- /dev/null +++ b/score/crypto/daemon/provider/executors/BUILD @@ -0,0 +1,33 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 = "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..cabcec7 --- /dev/null +++ b/score/crypto/daemon/provider/executors/key_mgmt_context.hpp @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..a414214 --- /dev/null +++ b/score/crypto/daemon/provider/executors/key_mgmt_executor.hpp @@ -0,0 +1,83 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..27d2a4e --- /dev/null +++ b/score/crypto/daemon/provider/executors/key_mgmt_request_parser.hpp @@ -0,0 +1,120 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..4169076 --- /dev/null +++ b/score/crypto/daemon/provider/executors/src/key_mgmt_executor.cpp @@ -0,0 +1,252 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +#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)) + { + score::mw::log::LogError() << LOG_PREFIX << "Execute: operation not yet implemented (0x" + << score::mw::log::LogHex16{action} << ")"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kUnsupportedOperation); + } + + score::mw::log::LogError() << LOG_PREFIX << "Execute: unsupported action 0x" << score::mw::log::LogHex16{action}; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "HandleGenerate: invalid request parameters"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "HandleGenerate: GenerateKey failed"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "HandleLoad: invalid slot node id"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "HandleLoad: slot resolution failed"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "HandleLoad: LoadOrShare failed"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "HandleRelease: invalid key node id"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "HandleRelease: release failed"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "HandleSlotInfo: invalid slot node id"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "HandleSlotInfo: slot resolution failed"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "HandleSlotInfo: GetSlotInfo failed"; + 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..d4fd046 --- /dev/null +++ b/score/crypto/daemon/provider/handler/BUILD @@ -0,0 +1,101 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 = "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..59e0932 --- /dev/null +++ b/score/crypto/daemon/provider/handler/context_data_node.hpp @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..30ddaa5 --- /dev/null +++ b/score/crypto/daemon/provider/handler/handler_init_params.hpp @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..97743b2 --- /dev/null +++ b/score/crypto/daemon/provider/handler/i_crypto_handler_factory.hpp @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..4a6a112 --- /dev/null +++ b/score/crypto/daemon/provider/handler/i_handler.hpp @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..c5e00d3 --- /dev/null +++ b/score/crypto/daemon/provider/handler/operations/hash_handler_operations.hpp @@ -0,0 +1,102 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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 InitHash(), initializes hash stream context, transitions state IDLE → INITIALIZED +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 INITIALIZED/ACTIVE → ACTIVE +inline constexpr OperationAction HASH_UPDATE = 2; + +// HASH_FINALIZE +// 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_FINALIZE = 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..844d3fc --- /dev/null +++ b/score/crypto/daemon/provider/handler/operations/mac_handler_operations.hpp @@ -0,0 +1,104 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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 InitMac(), initializes MAC stream context, transitions state IDLE → INITIALIZED +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 INITIALIZED/ACTIVE → ACTIVE +inline constexpr OperationAction MAC_UPDATE = 2; + +// TODO: Do we need here a final chunk? + +// MAC_FINALIZE +// 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_FINALIZE = 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..0d8eb2c --- /dev/null +++ b/score/crypto/daemon/provider/handler/src/context_data_node.cpp @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..6f684eb --- /dev/null +++ b/score/crypto/daemon/provider/handler/src/handler_utils.cpp @@ -0,0 +1,116 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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, + StreamOperation streamOperation) noexcept +{ + switch (streamOperation) + { + case StreamOperation::kInit: + // Init is allowed from any state; transitions to STREAM_INITIALIZED + return common::StreamOperationState::STREAM_INITIALIZED; + + case StreamOperation::kUpdate: + if (currentState == common::StreamOperationState::STREAM_INITIALIZED || + currentState == common::StreamOperationState::STREAM_ACTIVE) + { + return common::StreamOperationState::STREAM_ACTIVE; + } + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidStreamOperation); + + case StreamOperation::kFinalize: + if (currentState != common::StreamOperationState::STREAM_ACTIVE) + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidStreamOperation); + } + return common::StreamOperationState::IDLE; + } + + // Unreachable with a well-formed enum; satisfies compilers that warn on missing return + 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..abd4940 --- /dev/null +++ b/score/crypto/daemon/provider/handler/src/handler_utils.hpp @@ -0,0 +1,119 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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 + +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 Type-safe identifiers for streaming operation phases. +/// +/// Used by ValidateStreamOperationSequence to enforce the stream state machine +enum class StreamOperation : std::uint8_t +{ + kInit = 0, ///< Initialize or restart the streaming operation + kUpdate = 1, ///< Feed data into the active stream + kFinalize = 2, ///< Finalize the stream and produce output +}; + +/** + * @brief Validate a streaming operation and return the resulting next state. + * + * Enforces the stream state machine: + * - IDLE --(kInit)--> STREAM_INITIALIZED + * - STREAM_INITIALIZED --(kInit)--> STREAM_INITIALIZED (restart) + * - STREAM_INITIALIZED --(kUpdate)--> STREAM_ACTIVE + * - STREAM_ACTIVE --(kUpdate)--> STREAM_ACTIVE + * - STREAM_ACTIVE --(kInit)--> STREAM_INITIALIZED (restart) + * - STREAM_ACTIVE --(kFinalize)--> IDLE + * + * @param currentState The current operation state (IDLE, STREAM_INITIALIZED, or STREAM_ACTIVE) + * @param streamOperation The streaming operation being requested + * @return Expected containing the next StreamOperationState on success, or DaemonErrorCode on failure + * + * @retval StreamOperationState Transition valid; value is the resulting state + * @retval kInvalidStreamOperation UPDATE/FINALIZE attempted from invalid state + */ +[[nodiscard]] Expected +ValidateStreamOperationSequence(common::StreamOperationState currentState, StreamOperation streamOperation) 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..357b83c --- /dev/null +++ b/score/crypto/daemon/provider/i_provider.hpp @@ -0,0 +1,132 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..224a90f --- /dev/null +++ b/score/crypto/daemon/provider/i_provider_factory.hpp @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..0751bbb --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/BUILD @@ -0,0 +1,96 @@ +# ******************************************************************************* +# Copyright (c) 2026 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..22cbc9e --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/detail/pkcs11_algorithm_info.hpp @@ -0,0 +1,170 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..046f433 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_factory.cpp @@ -0,0 +1,234 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +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()) + { + score::mw::log::LogError() << LOG_PREFIX << "GenerateKey: unsupported algorithm '" << request.algorithm << "'"; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "C_GenerateKey failed: rv=" << static_cast(rv); + 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..6a70fdd --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_factory.hpp @@ -0,0 +1,78 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..2b0aaa2 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.cpp @@ -0,0 +1,86 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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/mw/log/logging.h" + +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() +{ + score::mw::log::LogDebug() << "[PKCS11_KEY_HANDLER] Release Key"; + 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..9c12895 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_handler.hpp @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..42d941f --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_slot_handler.cpp @@ -0,0 +1,285 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +#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()) + { + score::mw::log::LogError() << LOG_PREFIX << "LoadKey: slot '" << slot.slot_name + << "' has neither pkcs11.label nor pkcs11.object_id in deployment info"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "LoadKey: failed to acquire RO session for slot '" << slot.slot_name + << "'"; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "C_FindObjectsInit failed: rv=" << static_cast(rv); + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "C_FindObjects: object not found for slot '" << slot.slot_name + << "'"; + 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..6e1829a --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_slot_handler.hpp @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..e94d96a --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.cpp @@ -0,0 +1,250 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp" + +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp" + +#include "score/mw/log/logging.h" + +#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) + { + score::mw::log::LogError() << LOG_PREFIX << "ResolveObject: C_FindObjectsInit failed: rv=" + << static_cast(rv_init); + 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)) + { + score::mw::log::LogError() << LOG_PREFIX << "ResolveObject: token object not found on handler session"; + 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()) + { + score::mw::log::LogError() << LOG_PREFIX << "Release: opaque_id=" << opaque_id << " not found"; + 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) + { + score::mw::log::LogError() + << LOG_PREFIX << "C_DestroyObject failed: rv=" << static_cast(rv); + } + } + } + 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..9b4a941 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/key_management/pkcs11_key_store.hpp @@ -0,0 +1,198 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..82e6751 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/factory/pkcs11_handler_factory.cpp @@ -0,0 +1,140 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +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); + } + + score::mw::log::LogDebug() << "[PKCS11_HANDLER_FACTORY] Creating HASH handler for algorithm:" << algorithm; + + 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..dfb8fb7 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/factory/pkcs11_handler_factory.hpp @@ -0,0 +1,76 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..84a86ea --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_context.hpp @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..a8852f6 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.cpp @@ -0,0 +1,295 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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; + handler::handler_utils::StreamOperation op{}; + if (action == ops::HASH_INIT) + { + op = handler::handler_utils::StreamOperation::kInit; + } + else if (action == ops::HASH_UPDATE) + { + op = handler::handler_utils::StreamOperation::kUpdate; + } + else if (action == ops::HASH_FINALIZE) + { + op = handler::handler_utils::StreamOperation::kFinalize; + } + else + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidOperation); + } + const auto result = handler::handler_utils::ValidateStreamOperationSequence(currentState, op); + if (!result.has_value()) + { + return make_unexpected(result.error()); + } + nextState = result.value(); + return std::monostate{}; +} + +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_FINALIZE) + { + 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..78ad0d5 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_executor.hpp @@ -0,0 +1,115 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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_FINALIZE, 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..6dab367 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_handler.cpp @@ -0,0 +1,222 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +// 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_FINALIZE 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_FINALIZE) && 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..116e40d --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/hash/pkcs11_hash_handler.hpp @@ -0,0 +1,106 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..577d85e --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.cpp @@ -0,0 +1,73 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.hpp" + +#include "score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp" + +#include "score/mw/log/logging.h" + +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) + { + score::mw::log::LogError() << LOG_PREFIX << "Execute: executor not injected"; + 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..299344a --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/key_management/pkcs11_key_management_handler.hpp @@ -0,0 +1,100 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..cff0d88 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_context.hpp @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..bc918fd --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.cpp @@ -0,0 +1,646 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +#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_INITIALIZED) + { + 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_FINALIZE) + { + 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_INITIALIZED) + { + 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_FINALIZE 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_FINALIZE, 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_INITIALIZED); + 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; + handler::handler_utils::StreamOperation op{}; + if (action == ops::MAC_UPDATE) + { + op = handler::handler_utils::StreamOperation::kUpdate; + } + else if (action == ops::MAC_FINALIZE) + { + op = handler::handler_utils::StreamOperation::kFinalize; + } + else + { + return make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kInvalidOperation); + } + const auto result = handler::handler_utils::ValidateStreamOperationSequence(currentState, op); + if (!result.has_value()) + { + return make_unexpected(result.error()); + } + nextState = result.value(); + return std::monostate{}; +} + +// --------------------------------------------------------------------------- +// 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) + { + score::mw::log::LogError() << LOG_PREFIX << "C_SignInit failed: rv=" << static_cast(rv); + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "C_SignUpdate failed: rv=" << static_cast(rv); + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "C_SignFinal failed: rv=" << static_cast(rv); + 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) + { + score::mw::log::LogError() << LOG_PREFIX + << "C_SignUpdate (single-shot) failed: rv=" << static_cast(rv); + 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) + { + score::mw::log::LogError() << LOG_PREFIX + << "C_SignFinal (single-shot) failed: rv=" << static_cast(rv); + 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_INITIALIZED, 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_INITIALIZED): 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) + { + score::mw::log::LogError() << LOG_PREFIX + << "C_SignFinal (verify) failed: rv=" << static_cast(rv); + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "C_VerifyInit failed: rv=" << static_cast(rv); + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "C_VerifyUpdate failed: rv=" << static_cast(rv); + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "C_VerifyFinal failed: rv=" << static_cast(rv); + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "C_VerifyUpdate (single-shot verify) failed: rv=" + << static_cast(rv_upd); + 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..a007cbf --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_executor.hpp @@ -0,0 +1,214 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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_FINALIZE → 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_FINALIZE 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_INITIALIZED → 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_FINALIZE, 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_INITIALIZED (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_INITIALIZED), 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..1d75679 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_handler.cpp @@ -0,0 +1,260 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +#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) + { + score::mw::log::LogError() << LOG_PREFIX << "Unsupported algorithm:" << m_algorithm; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "Bound key provider ID " + << init_params.bound_key_handler->GetProviderId() + << " does not match expected provider ID" << init_params.provider_id; + score::mw::log::LogError() << LOG_PREFIX << "InitializeContext: bound key is not a PKCS#11 key handler"; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "InitializeContext: key is already in use by another handler"; + return score::crypto::make_unexpected(score::crypto::daemon::common::DaemonErrorCode::kResourceBusy); + } + if (!resolved.IsValid()) + { + score::mw::log::LogError() << LOG_PREFIX << "InitializeContext: failed to resolve key object handle"; + 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_INITIALIZED; + 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_INITIALIZED, 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) + { + score::mw::log::LogError() << LOG_PREFIX + << "Execute: no key bound - call InitializeContext with keyOpaqueId first"; + 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..50be7b5 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/operations/mac/pkcs11_mac_handler.hpp @@ -0,0 +1,130 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..a2b17b0 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_module.cpp @@ -0,0 +1,468 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp" + +#include "score/crypto/common/types.hpp" + +#include "score/crypto/daemon/common/daemon_error.hpp" +#include "score/mw/log/logging.h" +#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: + score::mw::log::LogError() + << "[PKCS11_MODULE] ERROR: Trying to map a success code to an error. This should not happen"; + 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..2061057 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_module.hpp @@ -0,0 +1,322 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..46c3064 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_provider.cpp @@ -0,0 +1,424 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +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; + score::mw::log::LogDebug() << "[PKCS#11] Provider (ID:" << m_numeric_id << ", Name:" << m_provider_name + << ") initialised successfully"; + 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()) + { + score::mw::log::LogError() << "[PKCS#11] Error: Failed to initialise module (error " + << static_cast(result.error()) << ")"; + 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()) + { + score::mw::log::LogError() << "[PKCS#11] Error: slotId is kSlotIdAutoDetect but tokenLabel is empty"; + return false; + } + + const auto result = Pkcs11Module::FindSlotByToken(*m_module, m_config.tokenLabel, m_config.tokenModel); + if (!result.has_value()) + { + score::mw::log::LogError() << "[PKCS#11] Error: Failed to find slot for token '" << m_config.tokenLabel + << "' (error:" << static_cast(result.error()) << ")"; + 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) + { + score::mw::log::LogError() << "[PKCS#11] Error: C_GetTokenInfo failed on slot" << m_config.slotId + << " (rv=" << static_cast(rv) << ")"; + 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()) + { + score::mw::log::LogError() << "[PKCS#11] Error: Failed to open initial session on slot" << m_config.slotId + << " (error" << static_cast(result.error()) << ")"; + 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..0fc2631 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_provider.hpp @@ -0,0 +1,198 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..f2bc0f8 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.cpp @@ -0,0 +1,66 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..08817d2 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_provider_factory.hpp @@ -0,0 +1,85 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..f5ee6d5 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_session_guard.hpp @@ -0,0 +1,139 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..1f1fda9 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_token_config.cpp @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..053f007 --- /dev/null +++ b/score/crypto/daemon/provider/pkcs11/pkcs11_token_config.hpp @@ -0,0 +1,100 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..ad7036b --- /dev/null +++ b/score/crypto/daemon/provider/provider_manager.hpp @@ -0,0 +1,285 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..433ab14 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/BUILD @@ -0,0 +1,50 @@ +# ******************************************************************************* +# Copyright (c) 2026 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") + +# 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..534f80b --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/BUILD @@ -0,0 +1,101 @@ +# ******************************************************************************* +# Copyright (c) 2026 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") + +# 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..df4fce7 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/detail/openssl_algorithm_info.hpp @@ -0,0 +1,88 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..6f365b5 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_factory.cpp @@ -0,0 +1,89 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..80393de --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_factory.hpp @@ -0,0 +1,75 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..0a7d8fe --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.cpp @@ -0,0 +1,85 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.hpp" + +#include "score/mw/log/logging.h" +#include // OPENSSL_cleanse +#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() +{ + score::mw::log::LogDebug() << "[OPENSSL_KEY_HANDLER] Release Key"; + 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..add53b1 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/key_management/openssl_key_handler.hpp @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..92fc4ae --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/openssl_provider_factory.cpp @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..47e1b4e --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/openssl_provider_factory.hpp @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..53def0e --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/factory/openssl_handler_factory.cpp @@ -0,0 +1,71 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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" + +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..6c96161 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/factory/openssl_handler_factory.hpp @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..ec23e7e --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/hash/openssl_hash_handler.cpp @@ -0,0 +1,380 @@ +/******************************************************************************** + * Copyright (c) 2026 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 +#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 "score/mw/log/logging.h" +#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) +{ + score::mw::log::LogDebug() << "DEBUG: InitializeContext called with algorithm:" << m_algorithm; + + // Validate the algorithm + const auto result = ValidateAlgorithm(m_algorithm); + if (!result.has_value()) + { + score::mw::log::LogError() << "ERROR: Algorithm validation failed in InitializeContext"; + 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::InitHash( + const std::optional initialDataOrIV) +{ + std::ostringstream tid; + tid << std::this_thread::get_id(); + score::mw::log::LogDebug() << "DEBUG: InitHash called with algorithm:" << m_algorithm << ", thread ID:" << tid.str() + << ", this:" << reinterpret_cast(this); + 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); + score::mw::log::LogDebug() << "[HASH_HANDLER] Output buffer allocated with size:" << size; +} + +} // 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..58c8dfb --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/hash/openssl_hash_handler.hpp @@ -0,0 +1,77 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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 InitHash( + 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..2544720 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/key_management/openssl_key_management_handler.cpp @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..f430f14 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/key_management/openssl_key_management_handler.hpp @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..b698959 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.cpp @@ -0,0 +1,420 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" +#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) + { + score::mw::log::LogError() << LOG_PREFIX << "Unsupported algorithm:" << m_algorithm; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "EVP_MAC_fetch(\"HMAC\") failed"; + return ::score::crypto::make_unexpected(::score::crypto::daemon::common::DaemonErrorCode::kAllocationFailed); + } + m_ctx = EVP_MAC_CTX_new(m_mac); + if (m_ctx == nullptr) + { + score::mw::log::LogError() << LOG_PREFIX << "EVP_MAC_CTX_new failed"; + 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 + { + score::mw::log::LogError() << LOG_PREFIX << "InitializeContext: bound key is not an OpenSSL key handler"; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "InitializeContext: invalid key handle"; + 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_INITIALIZED; + } + + return std::monostate{}; +} + +::score::crypto::Expected OpenSslHmacHandler::InitMac( + const std::optional /*initialDataOrIV*/) +{ + if (m_ctx == nullptr) + { + score::mw::log::LogError() << LOG_PREFIX << "InitMac: HMAC context not allocated"; + 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)) + { + score::mw::log::LogError() << LOG_PREFIX + << "InitMac: no valid key material \u2014 call InitializeContext with a key first"; + return ::score::crypto::make_unexpected( + ::score::crypto::daemon::common::DaemonErrorCode::kStreamNotInitialized); + } + + const char* digest_name = GetDigestName(); + if (digest_name == nullptr) + { + score::mw::log::LogError() << LOG_PREFIX << "InitMac: unsupported digest for algorithm" << m_algorithm; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "InitMac: EVP_MAC_init failed"; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "EVP_MAC_update failed"; + 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) + { + score::mw::log::LogError() << LOG_PREFIX << "EVP_MAC_final failed"; + 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..2fb897c --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/operations/mac/openssl_hmac_handler.hpp @@ -0,0 +1,111 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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 InitMac( + 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..8a838f0 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/provider_openssl.cpp @@ -0,0 +1,97 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" +#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)) + { + score::mw::log::LogError() << "[OpenSSL] Error: Failed to initialize OpenSSL"; + m_initialized = false; + return false; + } + score::mw::log::LogDebug() << "[OpenSSL] Initialized successfully (ID:" << m_numeric_id + << ", Name:" << m_provider_name << ")"; + + // 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..9de8dce --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/openssl/provider_openssl.hpp @@ -0,0 +1,63 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..cceae63 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/factory/BUILD @@ -0,0 +1,28 @@ +# ******************************************************************************* +# Copyright (c) 2026 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") + +# 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..73d7ead --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/factory/score_handler_factory.hpp @@ -0,0 +1,75 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..5fec9f3 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/factory/src/score_handler_factory.cpp @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..5eda4a4 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/hash/BUILD @@ -0,0 +1,35 @@ +# ******************************************************************************* +# Copyright (c) 2026 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") + +# 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..025f67a --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/hash/hash_executor.hpp @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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 ExecuteInit(ScoreHashHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected ExecuteUpdate(ScoreHashHandler& handler, + common::RequestParameters& request); + + [[nodiscard]] Expected ExecuteFinalize( + 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..cfc2c21 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/hash/score_hash_handler.hpp @@ -0,0 +1,127 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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 Initialize a hash operation on an existing context. + [[nodiscard]] virtual Expected InitHash( + 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..82a6f3b --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/hash/src/hash_executor.cpp @@ -0,0 +1,234 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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_FINALIZE) + { + auto result = ExecuteFinalize(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 ExecuteInit(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::ExecuteInit(ScoreHashHandler& handler, + RequestParameters& request) +{ + std::optional initialDataOrIV; + if (!request.empty()) + { + if (auto* buf = std::get_if(&request[0])) + { + initialDataOrIV.emplace(*buf); + } + } + return handler.InitHash(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::ExecuteFinalize(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) +{ + handler::handler_utils::StreamOperation op{}; + if (action == handler::hash_handler_operations::HASH_INIT) + { + op = handler::handler_utils::StreamOperation::kInit; + } + else if (action == handler::hash_handler_operations::HASH_UPDATE) + { + op = handler::handler_utils::StreamOperation::kUpdate; + } + else if (action == handler::hash_handler_operations::HASH_FINALIZE) + { + op = handler::handler_utils::StreamOperation::kFinalize; + } + else + { + return make_unexpected(DaemonErrorCode::kInvalidOperation); + } + const auto result = handler::handler_utils::ValidateStreamOperationSequence(currentState, op); + if (!result.has_value()) + { + return make_unexpected(result.error()); + } + nextState = result.value(); + return std::monostate{}; +} + +} // 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..e024056 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/hash/src/score_hash_handler.cpp @@ -0,0 +1,86 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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::InitHash( + 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..3d19e1a --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/key_management/BUILD @@ -0,0 +1,26 @@ +# ******************************************************************************* +# Copyright (c) 2026 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") + +# 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..e1d37d2 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/key_management/score_key_management_handler.hpp @@ -0,0 +1,63 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..995de9c --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/key_management/src/score_key_management_handler.cpp @@ -0,0 +1,62 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/crypto/daemon/provider/score_provider/operations/key_management/score_key_management_handler.hpp" + +#include "score/mw/log/logging.h" + +#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) + { + score::mw::log::LogError() << LOG_PREFIX << "Execute: executor not injected"; + 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..1fbf328 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/mac/BUILD @@ -0,0 +1,34 @@ +# ******************************************************************************* +# Copyright (c) 2026 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") + +# 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..210328f --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/mac/mac_executor.hpp @@ -0,0 +1,76 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..ef55fe4 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/mac/score_mac_handler.hpp @@ -0,0 +1,123 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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 Initialize the MAC stream context with the cached key. + [[nodiscard]] virtual Expected InitMac( + 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..0a93655 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/mac/src/mac_executor.cpp @@ -0,0 +1,268 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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_FINALIZE) + { + 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.InitMac(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.InitMac(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) +{ + handler::handler_utils::StreamOperation op{}; + if (action == handler::mac_handler_operations::MAC_INIT) + { + op = handler::handler_utils::StreamOperation::kInit; + } + else if (action == handler::mac_handler_operations::MAC_UPDATE) + { + op = handler::handler_utils::StreamOperation::kUpdate; + } + else if (action == handler::mac_handler_operations::MAC_FINALIZE) + { + op = handler::handler_utils::StreamOperation::kFinalize; + } + else + { + return make_unexpected(DaemonErrorCode::kInvalidOperation); + } + const auto result = handler::handler_utils::ValidateStreamOperationSequence(currentState, op); + if (!result.has_value()) + { + return make_unexpected(result.error()); + } + nextState = result.value(); + return std::monostate{}; +} + +} // 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..094fab7 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/operations/mac/src/score_mac_handler.cpp @@ -0,0 +1,80 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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::InitMac( + 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..4f07e88 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/score_provider.hpp @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..37f195a --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/score_provider_config.hpp @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..c191624 --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/score_provider_factory.hpp @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..da100bd --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/src/score_provider.cpp @@ -0,0 +1,66 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/crypto/daemon/provider/score_provider/score_provider.hpp" + +#include "score/mw/log/logging.h" + +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; + + score::mw::log::LogDebug() << "[ScoreProvider] Initialized (ID:" << m_numeric_id << ", Name:" << m_provider_name + << ")"; + 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..bdac8cd --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/src/score_provider_config.cpp @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..187ee6b --- /dev/null +++ b/score/crypto/daemon/provider/score_provider/src/score_provider_factory.cpp @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +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)) + { + score::mw::log::LogError() + << "[ScoreProviderFactory] Failed to create OpenSSL provider:" << entry.providerName; + all_ok = false; + } + } + else + { + score::mw::log::LogError() << "[ScoreProviderFactory] Unknown provider implementation: " + << entry.providerImpl; + 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..e66068a --- /dev/null +++ b/score/crypto/daemon/provider/src/provider_manager.cpp @@ -0,0 +1,280 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + +#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..6f795f6 --- /dev/null +++ b/score/crypto/daemon/src/daemon.cpp @@ -0,0 +1,132 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/mw/log/logging.h" +#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()) + { + score::mw::log::LogError() << "Warning: Could not parse config file (may not exist)"; + } + + 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 + score::mw::log::LogDebug() << "Termination requested, shutting down daemon..."; + server->Stop(); + server_thread.join(); + + score::mw::log::LogDebug() << "Daemon shutdown complete"; + return 0; +} diff --git a/score/crypto/ipc/BUILD b/score/crypto/ipc/BUILD new file mode 100644 index 0000000..4681932 --- /dev/null +++ b/score/crypto/ipc/BUILD @@ -0,0 +1,21 @@ +# ******************************************************************************* +# Copyright (c) 2026 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: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..3252073 --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/BUILD @@ -0,0 +1,76 @@ +# ******************************************************************************* +# Copyright (c) 2026 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: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..f280a7d --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/control.fbs @@ -0,0 +1,139 @@ +// ******************************************************************************* +// Copyright (c) 2026 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 +// ******************************************************************************* + +// 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..93f39cc --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/grpc_control_client.h @@ -0,0 +1,59 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +// 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..d27ff34 --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/grpc_control_handler.h @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +// 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..9a95c0f --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/grpc_control_server.h @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..65b576d --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/src/grpc_control_client.cpp @@ -0,0 +1,418 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" +#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::ostringstream tid; + tid << std::this_thread::get_id(); + score::mw::log::LogError() << "[GrpcControlClient] [Thread" << tid.str() << "] " + << "gRPC call failed for RequestID:" << request.request_id + << " | Error:" << status.error_message(); + 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) + { + score::mw::log::LogError() << " [MISMATCH!] RequestID mismatch - received: "; + 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. + + score::mw::log::LogError() + << "[GrpcControlClient] ERROR - Unsupported parameter type:" << static_cast(param_type); + 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. + + score::mw::log::LogError() << "[GrpcControlClient] ERROR - Unsupported parameter type in request"; + // 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..fcedae4 --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/src/grpc_control_handler.cpp @@ -0,0 +1,397 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" +#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::ostringstream tid; + tid << std::this_thread::get_id(); + + score::mw::log::LogDebug() << "[GrpcControlServiceAdapter] [Server Thread" << tid.str() << "] " + << "Processing request with RequestID:" << bl_req.request_id + << " | User Id:" << bl_req.client_id << " | DataNodeId:" << bl_req.data_node_id; + + // Lazy initialization: create handler for this thread on first request + if (!_thread_handler) + { + score::mw::log::LogDebug() << "[GrpcControlServiceAdapter] [Server Thread" << tid.str() << "] " + << "Initializing thread-local request handler"; + _thread_handler = _factory->CreateRequestHandler(); + } + + // Execute business logic (transport-agnostic) + auto bl_resp = _thread_handler->processRequest(bl_req); + + score::mw::log::LogDebug() << "[GrpcControlServiceAdapter] [Server Thread" << tid.str() << "] " + << "Processed request, returning response with RequestID:" << bl_resp.request_id + << " (Request had:" << bl_req.request_id << ")"; + + // 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. + + score::mw::log::LogError() << "[GrpcControlServiceAdapter] ERROR - Unsupported parameter type in response"; + // 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. + + score::mw::log::LogError() << "[GrpcControlServiceAdapter] ERROR - Unsupported parameter type: " + << static_cast(param_type); + 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..bd6e90e --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/src/grpc_control_server.cpp @@ -0,0 +1,107 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "score/crypto/ipc/grpc_adapter/grpc_control_server.h" +#include "score/crypto/ipc/grpc_adapter/grpc_control_handler.h" +#include "score/mw/log/logging.h" +#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)); + } + + score::mw::log::LogWarn() << "[GrpcControlServer] !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; + score::mw::log::LogWarn() << "[GrpcControlServer] !!! WARNING: Using insecure mechanism for uid and pid !!!"; + score::mw::log::LogWarn() << "[GrpcControlServer] !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; + score::mw::log::LogDebug() << "[GrpcControlServer] Listening on unix:" << _impl->socket_path; +} + +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) + { + score::mw::log::LogDebug() << "[GrpcControlServer] Cleaned up socket file:" << _impl->socket_path; + } + else if (errno != ENOENT) + { + score::mw::log::LogError() << "[GrpcControlServer] Warning: Failed to remove socket file " + << _impl->socket_path << ":" << strerror(errno); + } + _impl->socket_path.clear(); + } + + score::mw::log::LogDebug() << "[GrpcControlServer] gRPC Control Server shutdown complete"; + } +} + +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..cd404bd --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/src/grpc_id_helpers.cpp @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (c) 2026 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 +#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..d7f1b05 --- /dev/null +++ b/score/crypto/ipc/grpc_adapter/src/grpc_id_helpers.h @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..d4d5944 --- /dev/null +++ b/score/crypto/ipc/ipc_config.h @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..19672a5 --- /dev/null +++ b/score/mw/crypto/api/BUILD @@ -0,0 +1,45 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 = "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..3222479 --- /dev/null +++ b/score/mw/crypto/api/common/BUILD @@ -0,0 +1,53 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 = "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..cc92bda --- /dev/null +++ b/score/mw/crypto/api/common/crypto_resource_guard.hpp @@ -0,0 +1,145 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..ad3b316 --- /dev/null +++ b/score/mw/crypto/api/common/error_domain.hpp @@ -0,0 +1,151 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..554132b --- /dev/null +++ b/score/mw/crypto/api/common/fixed_capacity_string.hpp @@ -0,0 +1,328 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..dfe1a19 --- /dev/null +++ b/score/mw/crypto/api/common/i_memory_allocator.hpp @@ -0,0 +1,86 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..932d980 --- /dev/null +++ b/score/mw/crypto/api/common/i_memory_region.hpp @@ -0,0 +1,104 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..c36218e --- /dev/null +++ b/score/mw/crypto/api/common/src/crypto_resource_guard.cpp @@ -0,0 +1,104 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..a3026f2 --- /dev/null +++ b/score/mw/crypto/api/common/src/crypto_resource_guard_factory.hpp @@ -0,0 +1,108 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..9f20510 --- /dev/null +++ b/score/mw/crypto/api/common/src/error_domain.cpp @@ -0,0 +1,157 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..b03950a --- /dev/null +++ b/score/mw/crypto/api/common/src/i_release_callback.hpp @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..5ca9959 --- /dev/null +++ b/score/mw/crypto/api/common/types.hpp @@ -0,0 +1,443 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..eb24fcb --- /dev/null +++ b/score/mw/crypto/api/config/BUILD @@ -0,0 +1,31 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 = "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..ae2a9ad --- /dev/null +++ b/score/mw/crypto/api/config/base_context_config.hpp @@ -0,0 +1,193 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..bab0d0a --- /dev/null +++ b/score/mw/crypto/api/config/hash_context_config.hpp @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..ba49d70 --- /dev/null +++ b/score/mw/crypto/api/config/key_management_context_config.hpp @@ -0,0 +1,66 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..b133fe3 --- /dev/null +++ b/score/mw/crypto/api/config/key_operation_params.hpp @@ -0,0 +1,586 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..7c096e1 --- /dev/null +++ b/score/mw/crypto/api/config/mac_context_config.hpp @@ -0,0 +1,86 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..6177e13 --- /dev/null +++ b/score/mw/crypto/api/config/permission_builder.hpp @@ -0,0 +1,154 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..d29b011 --- /dev/null +++ b/score/mw/crypto/api/contexts/BUILD @@ -0,0 +1,72 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 = "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..f48d67e --- /dev/null +++ b/score/mw/crypto/api/contexts/i_context.hpp @@ -0,0 +1,54 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..5ebe37a --- /dev/null +++ b/score/mw/crypto/api/contexts/i_hash_context.hpp @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..7f8f4b6 --- /dev/null +++ b/score/mw/crypto/api/contexts/i_key_management_context.hpp @@ -0,0 +1,300 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..fb4013b --- /dev/null +++ b/score/mw/crypto/api/contexts/i_mac_context.hpp @@ -0,0 +1,76 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..571ee0b --- /dev/null +++ b/score/mw/crypto/api/contexts/i_streaming_context.hpp @@ -0,0 +1,107 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..3ac74d4 --- /dev/null +++ b/score/mw/crypto/api/contexts/i_streaming_output_context.hpp @@ -0,0 +1,71 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..3e0b600 --- /dev/null +++ b/score/mw/crypto/api/contexts/src/hash_context_impl.cpp @@ -0,0 +1,356 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" +#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) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR: Connection is not initialized during destruction"; + 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()) + { + score::mw::log::LogError() + << "[API][HashContextImpl] ERROR: Failed to build CTX_CLOSE request during destruction"; + 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()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR: CTX_CLOSE response validation failed: " + << validator.getError(); + 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()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR: Failed to build HASH_INIT request"; + 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()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR:" << validator.getError(); + // 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()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR: Failed to build HASH_UPDATE request"; + 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()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR:" << validator.getError(); + 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_FINALIZE}) + .build(); + if (!control_req_result.has_value()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR: Failed to build HASH_FINALIZE request"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "Failed to build HASH_FINALIZE request")}; + } + + // Send HASH_FINALIZE request to daemon + auto control_response_res = m_connection->SendRequest(control_req_result.value()); + + // Validate HASH_FINALIZE response + auto validator = proto::ControlResponseValidator::FromResult(control_response_res); + validator.expectOperation({actors::OP_ACTOR_HASH_HANDLER, hash_ops::HASH_FINALIZE}).expectSuccess(); + + if (!validator.isValid()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR:" << validator.getError(); + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "HASH_FINALIZE daemon response invalid")}; + } + + auto hash_result = validator.getParameterAt(0, 0); + if (!hash_result.has_value()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR: HASH_FINALIZE response has invalid parameter type"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kOperationFailed, "HASH_FINALIZE 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()) + { + score::mw::log::LogError() + << "[API][HashContextImpl] ERROR: Output buffer too small for full hash, truncated copy performed"; + 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()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR: Failed to build HASH_SS request"; + 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()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR:" << validator.getError(); + 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()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR: HASH_SS response has invalid parameter type"; + 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()) + { + score::mw::log::LogError() + << "[API][HashContextImpl] ERROR: Output buffer too small for full hash, truncated copy performed"; + 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()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR: Failed to build HASH_RESET request"; + 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()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR:" << validator.getError(); + 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()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR: Failed to build HASH_GET_DIGEST_SIZE request"; + 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()) + { + score::mw::log::LogError() << "[API][HashContextImpl] ERROR:" << validator.getError(); + return 0; + } + + auto size_result = validator.getParameterAt(0, 0); + if (!size_result.has_value()) + { + score::mw::log::LogError() + << "[API][HashContextImpl] ERROR: HASH_GET_DIGEST_SIZE response has invalid parameter type"; + 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..32efaaa --- /dev/null +++ b/score/mw/crypto/api/contexts/src/hash_context_impl.hpp @@ -0,0 +1,76 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..ff33dd2 --- /dev/null +++ b/score/mw/crypto/api/contexts/src/key_management_context_impl.cpp @@ -0,0 +1,423 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" +#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) + { + score::mw::log::LogError() + << "[API][KeyMgmtContextImpl] ERROR: Connection is not initialized during CTX_CLOSE"; + 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()) + { + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: Failed to build CTX_CLOSE request"; + 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()) + { + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: CTX_CLOSE response validation failed: " + << validator.getError(); + 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()) + { + score::mw::log::LogError() << "[API][KeyMgmtRelease] ERROR: Failed to build KEY_RELEASE request"; + 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()) + { + score::mw::log::LogError() << "[API][KeyMgmtRelease] ERROR:" << validator.getError(); + 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()) + { + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: Failed to build KEY_GENERATE request"; + 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()) + { + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR:" << validator.getError(); + 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()) + { + score::mw::log::LogError() + << "[API][KeyMgmtContextImpl] ERROR: KEY_GENERATE response has invalid resource_id type"; + 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) + { + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: LoadKey called with invalid slot resource type"; + 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()) + { + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: Failed to build KEY_LOAD request"; + 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()) + { + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR:" << validator.getError(); + 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()) + { + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: KEY_LOAD response has invalid resource_id type"; + 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*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: GenerateKey with target_slot not yet implemented"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kUnsupportedOperation, "GenerateKey with target_slot not yet implemented")}; +} + +score::Result KeyManagementContextImpl::DeriveKey(const DeriveKeyParams& /*params*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: DeriveKey not yet implemented"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "DeriveKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::DeriveKey(const CryptoResourceId& /*target_slot*/, + const DeriveKeyParams& /*params*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: DeriveKey not yet implemented"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "DeriveKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::AgreeKey(const AgreeKeyParams& /*params*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: AgreeKey not yet implemented"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "AgreeKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::AgreeKey(const CryptoResourceId& /*target_slot*/, + const AgreeKeyParams& /*params*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: AgreeKey not yet implemented"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "AgreeKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::PersistKey(const CryptoResourceId& /*target_slot*/, + const CryptoResourceId& /*ephemeral_key*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: PersistKey not yet implemented"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "PersistKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::UnwrapKey(const UnwrapKeyParams& /*params*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: UnwrapKey not yet implemented"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "UnwrapKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::UnwrapKey(const CryptoResourceId& /*target_slot*/, + const UnwrapKeyParams& /*params*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: UnwrapKey not yet implemented"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "UnwrapKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::ImportKey(const ImportKeyParams& /*params*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: ImportKey not yet implemented"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "ImportKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::ImportKey(const CryptoResourceId& /*target_slot*/, + const ImportKeyParams& /*params*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: ImportKey not yet implemented"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "ImportKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::WrapKey(const WrapKeyParams& /*params*/, + score::cpp::span /*output*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: WrapKey not yet implemented"; + return score::Result{score::unexpect, + MakeError(CryptoErrorCode::kUnsupportedOperation, "WrapKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::GetWrapKeySize(const WrapKeyParams& /*params*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: GetWrapKeySize not yet implemented"; + 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*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: ExportKey not yet implemented"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "ExportKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::GetExportKeySize(const CryptoResourceId& /*key*/, + FormatType /*format*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: GetExportKeySize not yet implemented"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "GetExportKeySize not yet implemented")}; +} + +score::Result KeyManagementContextImpl::ClearKey(const CryptoResourceId& /*key*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: ClearKey not yet implemented"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kUnsupportedOperation, "ClearKey not yet implemented")}; +} + +score::Result KeyManagementContextImpl::GetKeySlotInfo(const CryptoResourceId& /*slot*/) +{ + score::mw::log::LogError() << "[API][KeyMgmtContextImpl] ERROR: GetKeySlotInfo not yet implemented"; + 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..520b201 --- /dev/null +++ b/score/mw/crypto/api/contexts/src/key_management_context_impl.hpp @@ -0,0 +1,142 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..c39f684 --- /dev/null +++ b/score/mw/crypto/api/contexts/src/mac_context_impl.cpp @@ -0,0 +1,312 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" +#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) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR: Connection is not initialized during destruction"; + 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()) + { + score::mw::log::LogError() + << "[API][MacContextImpl] ERROR: Failed to build CTX_CLOSE request during destruction"; + 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()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR: CTX_CLOSE response validation failed: " + << validator.getError(); + 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()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR: Failed to build MAC_UPDATE request"; + 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()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR:" << validator.getError(); + 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_FINALIZE}) + .build(); + if (!control_req_result.has_value()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR: Failed to build MAC_FINALIZE request"; + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "Failed to build MAC_FINALIZE 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_FINALIZE}).expectSuccess(); + + if (!validator.isValid()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR:" << validator.getError(); + return score::Result{ + score::unexpect, MakeError(CryptoErrorCode::kOperationFailed, "MAC_FINALIZE daemon response invalid")}; + } + + auto mac_result = validator.getParameterAt(0, 0); + if (!mac_result.has_value()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR: MAC_FINALIZE response has invalid parameter type"; + return score::Result{ + score::unexpect, + MakeError(CryptoErrorCode::kOperationFailed, "MAC_FINALIZE 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()) + { + score::mw::log::LogError() + << "[API][MacContextImpl] ERROR: Output buffer too small for full MAC tag, truncated copy performed"; + 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()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR: Failed to build MAC_VERIFY request"; + 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()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR:" << validator.getError(); + 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()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR: MAC_VERIFY response has invalid parameter type"; + 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()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR: Failed to build MAC_RESET request"; + 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()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR:" << validator.getError(); + 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()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR: Failed to build MAC_GET_SIZE request"; + 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()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR:" << validator.getError(); + return 0; + } + + auto size_result = validator.getParameterAt(0, 0); + if (!size_result.has_value()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR: MAC_GET_SIZE response has invalid parameter type"; + 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()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR: Failed to build MAC_INIT request"; + 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()) + { + score::mw::log::LogError() << "[API][MacContextImpl] ERROR:" << validator.getError(); + 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..9cb9d97 --- /dev/null +++ b/score/mw/crypto/api/contexts/src/mac_context_impl.hpp @@ -0,0 +1,75 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..0ad2a66 --- /dev/null +++ b/score/mw/crypto/api/crypto_stack_factory.hpp @@ -0,0 +1,89 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..f809ddf --- /dev/null +++ b/score/mw/crypto/api/future/certificate/BUILD @@ -0,0 +1,32 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* + +# ============================================================================= +# 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..4d90dbc --- /dev/null +++ b/score/mw/crypto/api/future/certificate/cert_types.hpp @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..80ff43e --- /dev/null +++ b/score/mw/crypto/api/future/certificate/i_csr_export.hpp @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..8e6e7d7 --- /dev/null +++ b/score/mw/crypto/api/future/certificate/i_ocsp_request_export.hpp @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..0eedff2 --- /dev/null +++ b/score/mw/crypto/api/future/common/BUILD @@ -0,0 +1,27 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 = "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..72f5fed --- /dev/null +++ b/score/mw/crypto/api/future/common/i_secure_storage_manager.hpp @@ -0,0 +1,94 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..77c7772 --- /dev/null +++ b/score/mw/crypto/api/future/config/BUILD @@ -0,0 +1,36 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* + +# ============================================================================= +# 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..542ec36 --- /dev/null +++ b/score/mw/crypto/api/future/config/aead_context_config.hpp @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..75c875f --- /dev/null +++ b/score/mw/crypto/api/future/config/certificate_context_config.hpp @@ -0,0 +1,71 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..1f9ea25 --- /dev/null +++ b/score/mw/crypto/api/future/config/certificate_verification_context_config.hpp @@ -0,0 +1,90 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..69af7e3 --- /dev/null +++ b/score/mw/crypto/api/future/config/cipher_context_config.hpp @@ -0,0 +1,104 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..ba83fc1 --- /dev/null +++ b/score/mw/crypto/api/future/config/csr_generation_context_config.hpp @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..53d6127 --- /dev/null +++ b/score/mw/crypto/api/future/config/random_context_config.hpp @@ -0,0 +1,71 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..4a56e40 --- /dev/null +++ b/score/mw/crypto/api/future/config/sign_context_config.hpp @@ -0,0 +1,80 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..7ab38b1 --- /dev/null +++ b/score/mw/crypto/api/future/config/verify_signature_context_config.hpp @@ -0,0 +1,80 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..58dd1bb --- /dev/null +++ b/score/mw/crypto/api/future/contexts/BUILD @@ -0,0 +1,79 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* + +# ============================================================================= +# 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..dd269e9 --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_aead_context.hpp @@ -0,0 +1,105 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..50b1bc5 --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_certificate_management_context.hpp @@ -0,0 +1,247 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..e85e262 --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_certificate_verification_context.hpp @@ -0,0 +1,156 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..c5a5498 --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_cipher_context.hpp @@ -0,0 +1,119 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..ef716b1 --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_csr_generation_context.hpp @@ -0,0 +1,105 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..0962ac9 --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_random_context.hpp @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..57c1a98 --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_sign_context.hpp @@ -0,0 +1,85 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..16adc7e --- /dev/null +++ b/score/mw/crypto/api/future/contexts/i_verify_signature_context.hpp @@ -0,0 +1,78 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..d665bda --- /dev/null +++ b/score/mw/crypto/api/future/contexts/src/i_certificate_context_impl_guide.hpp @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..054dcbe --- /dev/null +++ b/score/mw/crypto/api/future/objects/BUILD @@ -0,0 +1,34 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 +# ******************************************************************************* + +# ============================================================================= +# 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..731731f --- /dev/null +++ b/score/mw/crypto/api/future/objects/i_cert_slot_object.hpp @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..1dfc0b6 --- /dev/null +++ b/score/mw/crypto/api/future/objects/i_certificate_object.hpp @@ -0,0 +1,109 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..0e26261 --- /dev/null +++ b/score/mw/crypto/api/future/objects/i_data_object.hpp @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..b0a112f --- /dev/null +++ b/score/mw/crypto/api/future/objects/i_provider_object.hpp @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..0d45b56 --- /dev/null +++ b/score/mw/crypto/api/future/objects/i_secure_object.hpp @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..9469636 --- /dev/null +++ b/score/mw/crypto/api/i_crypto_context.hpp @@ -0,0 +1,242 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..a37f166 --- /dev/null +++ b/score/mw/crypto/api/i_crypto_stack.hpp @@ -0,0 +1,77 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..75cbe2c --- /dev/null +++ b/score/mw/crypto/api/objects/BUILD @@ -0,0 +1,33 @@ +# ******************************************************************************* +# Copyright (c) 2026 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 = "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..24e588f --- /dev/null +++ b/score/mw/crypto/api/objects/i_crypto_object.hpp @@ -0,0 +1,62 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..6d55a79 --- /dev/null +++ b/score/mw/crypto/api/objects/i_key_object.hpp @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..8290ad0 --- /dev/null +++ b/score/mw/crypto/api/objects/i_key_slot_object.hpp @@ -0,0 +1,82 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..06fe839 --- /dev/null +++ b/score/mw/crypto/api/objects/i_private_key_object.hpp @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..1da5734 --- /dev/null +++ b/score/mw/crypto/api/objects/i_public_key_object.hpp @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..10d8f94 --- /dev/null +++ b/score/mw/crypto/api/objects/i_symmetric_key_object.hpp @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..e1fe1ba --- /dev/null +++ b/score/mw/crypto/api/src/crypto_context_impl.cpp @@ -0,0 +1,408 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" +#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()) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: Failed to build CTX_CREATE request"; + 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()) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR:" << validator.getError(); + 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()) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: CTX_CREATE response has invalid context_id type"; + 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()) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: Failed to build RESOURCE_RESOLVE request"; + 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()) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR:" << validator.getError(); + 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()) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: RESOURCE_RESOLVE response missing resource_id"; + 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()) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: RESOURCE_RESOLVE response missing type"; + 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()) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: RESOURCE_RESOLVE response missing persistence"; + 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()) + { + score::mw::log::LogError() + << "[API][CryptoContextImpl] ERROR: RESOURCE_RESOLVE response missing primary_provider"; + 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) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: CreateMacContext invalid / missing key id"; + return score::Result>{ + score::unexpect, + MakeError(CryptoErrorCode::kContextCreationFailed, "CreateMacContext invalid / missing key id")}; + } + + if (config.key.type != ResourceType::kKey && config.key.type != ResourceType::kKeySlot) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: CreateMacContext invalid key type"; + 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()) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: Failed to build CTX_CREATE request for MAC"; + 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()) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR:" << validator.getError(); + 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()) + { + score::mw::log::LogError() + << "[API][CryptoContextImpl] ERROR: CTX_CREATE MAC response has invalid context_id type"; + 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()) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: Failed to build CTX_CREATE request for KEY_MGMT"; + 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()) + { + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR:" << validator.getError(); + 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()) + { + score::mw::log::LogError() + << "[API][CryptoContextImpl] ERROR: CTX_CREATE KEY_MGMT response has invalid context_id type"; + 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 + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: QueryCapabilities(algorithm) not yet implemented"; + 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 + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: QueryCapabilities() not yet implemented"; + 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 + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: GetProviderInfo not yet implemented"; + 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 + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: GetProviderInfo not yet implemented"; + 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 + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: GetKeyObject not yet implemented"; + 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 + score::mw::log::LogError() << "[API][CryptoContextImpl] ERROR: GetKeySlotObject not yet implemented"; + 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..5418a19 --- /dev/null +++ b/score/mw/crypto/api/src/crypto_context_impl.hpp @@ -0,0 +1,82 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..24bdb19 --- /dev/null +++ b/score/mw/crypto/api/src/crypto_stack_factory.cpp @@ -0,0 +1,107 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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..66b98e9 --- /dev/null +++ b/score/mw/crypto/api/src/crypto_stack_impl.cpp @@ -0,0 +1,78 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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 "score/mw/log/logging.h" + +#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) + { + score::mw::log::LogError() << "[API][CryptoStackImpl] ERROR: Connection is not initialized"; + 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. + score::mw::log::LogError() << "[API][CryptoStackImpl] ERROR: GetMemoryAllocator not yet implemented"; + 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..e28032c --- /dev/null +++ b/score/mw/crypto/api/src/crypto_stack_impl.hpp @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..c1e4df0 --- /dev/null +++ b/score/mw/crypto/api/src/provider_type_converter.hpp @@ -0,0 +1,57 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +#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..8d7873c --- /dev/null +++ b/tests/README.md @@ -0,0 +1,71 @@ +# 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 | +| `config/` | Config | — | Shared runtime configs (e.g. `logging.json` for `mw::log`) | +| `test_vectors/` | Data | — | Known-answer test vectors (HMAC, AES, etc.) | +| `utility/` | Component | Google Test | Shared test utilities and helpers | + +### Logging configuration + +`tests/config/logging.json` configures `score::mw::log` for local development. It enables +verbose console output so all `LogDebug()` / `LogVerbose()` messages are visible. + +Set the env var before running the daemon or any test binary: + +```sh +export MW_LOG_CONFIG_FILE=tests/config/logging.json +``` + +To suppress debug output, simply omit the env var (the library default is `kWarn`). + +### 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/rust/BUILD b/tests/config/BUILD similarity index 73% rename from tests/rust/BUILD rename to tests/config/BUILD index 828a001..c4dfdb1 100644 --- a/tests/rust/BUILD +++ b/tests/config/BUILD @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -10,9 +10,5 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@rules_rust//rust:defs.bzl", "rust_test") -rust_test( - name = "rust_hello_test", - srcs = ["test_main.rs"], -) +exports_files(["logging.json"]) diff --git a/tests/config/logging.json b/tests/config/logging.json new file mode 100644 index 0000000..952dda0 --- /dev/null +++ b/tests/config/logging.json @@ -0,0 +1,6 @@ +{ + "appId": "CRYP", + "logLevel": "kVerbose", + "logMode": "kConsole", + "logLevelThresholdConsole": "kVerbose" +} 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..2350876 --- /dev/null +++ b/tests/demo/BUILD @@ -0,0 +1,30 @@ +# ******************************************************************************* +# Copyright (c) 2026 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: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..42473b5 --- /dev/null +++ b/tests/demo/mac_multi_provider_demo.cpp @@ -0,0 +1,338 @@ +/******************************************************************************** + * Copyright (c) 2026 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 + ********************************************************************************/ + +/// @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_FINALIZE → +/// 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_INITIALIZED 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->InitMac(std::nullopt); + ASSERT_TRUE(init_result.has_value()) << "InitMac 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->InitMac(std::nullopt); + ASSERT_TRUE(init_result.has_value()) << "InitMac 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..fe5130b --- /dev/null +++ b/tests/grpc_control_plane/BUILD @@ -0,0 +1,30 @@ +# ******************************************************************************* +# Copyright (c) 2026 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_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..49f095c --- /dev/null +++ b/tests/grpc_control_plane/test_control_plane.cpp @@ -0,0 +1,227 @@ +/******************************************************************************** + * Copyright (c) 2026 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 +#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..0d40463 --- /dev/null +++ b/tests/integration_tests/BUILD @@ -0,0 +1,104 @@ +# ******************************************************************************* +# Copyright (c) 2026 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_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", + "//tests/config:logging.json", + "//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..67a2c64 --- /dev/null +++ b/tests/integration_tests/DEMO.md @@ -0,0 +1,41 @@ +# 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 +export MW_LOG_CONFIG_FILE=tests/config/logging.json + +./bazel-bin/score/crypto/daemon/crypto_daemon +``` + +> `MW_LOG_CONFIG_FILE` points to the logging config in `tests/config/logging.json`. +> It enables verbose console logging (`kVerbose`). Omit it to fall back to the default log level (`kWarn`) — debug messages will not be shown. + +## 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..8253679 --- /dev/null +++ b/tests/integration_tests/control_client_app.cpp @@ -0,0 +1,588 @@ +/******************************************************************************** + * Copyright (c) 2026 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 "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_FINALIZE + 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_FINALIZE}) + .build(); + + if (!finishResponse.has_value()) + { + CloseContext(_connection.get(), context_id); + ASSERT_TRUE(false) << "Failed to build HASH_FINALIZE 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_FINALIZE}) + .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..5f20a44 --- /dev/null +++ b/tests/integration_tests/init_softhsm_token.cpp @@ -0,0 +1,396 @@ +/******************************************************************************** + * Copyright (c) 2026 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