Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/README_WORKFLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ flowchart TB
| proteccio | ✓ | ✗ | FIPS only |
| softhsm2 | ✓ | ✓ | |

> **Upstream-only jobs**: test types that need upstream-only secrets or infrastructure run in
> dedicated jobs gated by `if: github.repository == 'Cosmian/kms'`, so they are skipped on
> forks: `test-nix-upstream` (`google-cse`, `secret_vault`, `secret_aws`, `secret_azure`),
> `hsm-upstream` (`proteccio`, `crypt2pay`, `aws-cloudhsm` hardware HSMs) and `xks-remote`
> (AWS XKS — remote server). They are separate jobs
> because a job-level `if` cannot read the `matrix` context.

---

## 5. Windows Test Workflow (`test_windows.yml`)
Expand Down
188 changes: 136 additions & 52 deletions .github/workflows/test_all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ jobs:
- mariadb
- psql
- otel
- google-cse
- redis
- pykmip
- wasm
Expand All @@ -38,9 +37,6 @@ jobs:
- iris
- db2
- ase
- secret_vault
- secret_aws
- secret_azure
- secret_cosmian_kms
- spire
- kmip-go
Expand Down Expand Up @@ -89,13 +85,8 @@ jobs:
- type: ase
features: fips
# secret_cosmian_kms runs against a local KMS server — works with both fips and non-fips
# secret_vault, secret_aws, secret_azure require external services — run non-fips only
- type: secret_vault
features: fips
- type: secret_aws
features: fips
- type: secret_azure
features: fips
# google-cse, secret_vault, secret_aws and secret_azure need upstream-only secrets:
# they run in the `test-nix-upstream` job, which is skipped on forks.
# spire relies on the Vault API which is non-fips only
- type: spire
features: fips
Expand Down Expand Up @@ -222,40 +213,145 @@ jobs:
run: |
mise run test:pkcs11:support --variant non-fips

# Test types that depend on upstream-only secrets / external services. Kept out of
# `test-nix` because a job-level `if` cannot read the `matrix` context: the whole
# job is skipped on forks, where those secrets do not exist.
test-nix-upstream:
name: Test on ${{ matrix.type }} - ${{ matrix.features }}
runs-on: ubuntu-latest
if: github.repository == 'Cosmian/kms'
strategy:
fail-fast: false
matrix:
type:
- google-cse
- secret_vault
- secret_aws
- secret_azure
features: [fips, non-fips]
exclude:
# secret_vault, secret_aws, secret_azure require external services — run non-fips only
- type: secret_vault
features: fips
- type: secret_aws
features: fips
- type: secret_azure
features: fips

steps:
- uses: actions/checkout@v7
with:
submodules: recursive

- uses: ./.github/actions/cleanup-runner

- uses: ./.github/actions/setup-nix

- uses: ./.github/actions/install-mise

- name: Test
env:
# Google variables (google-cse test type)
TEST_GOOGLE_OAUTH_CLIENT_ID: ${{ secrets.TEST_GOOGLE_OAUTH_CLIENT_ID }}
TEST_GOOGLE_OAUTH_CLIENT_SECRET: ${{ secrets.TEST_GOOGLE_OAUTH_CLIENT_SECRET }}
TEST_GOOGLE_OAUTH_REFRESH_TOKEN: ${{ secrets.TEST_GOOGLE_OAUTH_REFRESH_TOKEN }}
GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY }}

# AWS secret backend variables (secret_aws test type)
AWS_ACCESS_KEY_ID: ${{ secrets.KMS_CI_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.KMS_CI_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.KMS_CI_AWS_REGION }}

# Azure Key Vault secret backend variables (secret_azure test type)
AZURE_TENANT_ID: ${{ secrets.KMS_CI_AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.KMS_CI_AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.KMS_CI_AZURE_CLIENT_SECRET }}
AZURE_KV_NAME: ${{ secrets.KMS_CI_AZURE_KV_NAME }}

# Provide an authenticated token so mise can install tools from
# GitHub releases without hitting the unauthenticated rate limit.
GITHUB_TOKEN: ${{ github.token }}
run: |
set -ex
mise run test:${{ matrix.type }} --variant ${{ matrix.features }}

hsm:
name: HSM ${{ matrix.hsm-type }} - ${{ matrix.features }}
runs-on: ubuntu-latest
# proteccio, crypt2pay, and aws-cloudhsm hardware do not support concurrent connections
# from multiple CI runs; give them fixed concurrency groups so only one job runs at a time
# across all PRs. utimaco and softhsm2 use a per-run group so they are never blocked by
# other PRs.
# Software/simulated HSMs: a per-run group so they are never blocked by other PRs.
# Hardware HSMs (proteccio, crypt2pay, aws-cloudhsm) run in the `hsm-upstream` job.
concurrency:
group: ${{ (matrix.hsm-type == 'proteccio' && 'hsm-proteccio') || (matrix.hsm-type == 'crypt2pay' && 'hsm-crypt2pay') || (matrix.hsm-type == 'aws-cloudhsm'
&& 'hsm-aws-cloudhsm') || format('hsm-{0}-{1}', matrix.hsm-type, github.run_id) }}
group: ${{ format('hsm-{0}-{1}', matrix.hsm-type, github.run_id) }}
queue: max
cancel-in-progress: false
strategy:
fail-fast: false
matrix:
hsm-type:
- utimaco
- proteccio
- softhsm2
- kryoptic
features: [fips, non-fips]

steps:
- uses: actions/checkout@v7
with:
submodules: recursive

- uses: ./.github/actions/cleanup-runner

- uses: ./.github/actions/setup-nix

- uses: ./.github/actions/install-mise

- name: Test
env:
# Google variables
TEST_GOOGLE_OAUTH_CLIENT_ID: ${{ secrets.TEST_GOOGLE_OAUTH_CLIENT_ID }}
TEST_GOOGLE_OAUTH_CLIENT_SECRET: ${{ secrets.TEST_GOOGLE_OAUTH_CLIENT_SECRET }}
TEST_GOOGLE_OAUTH_REFRESH_TOKEN: ${{ secrets.TEST_GOOGLE_OAUTH_REFRESH_TOKEN }}
GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY }}
run: |
mise run test:hsm-${{ matrix.hsm-type }} --variant ${{ matrix.features }}

# PKCS#11 v3 conformance tests: signs through OpenSC's pkcs11-tool (a real,
# independently-implemented external PKCS#11 v3 client), not just our own
# in-process Rust tests above. SoftHSM2-only: it is the only backend the CI
# is allowed to reconfigure with an HSM-KEK for this check.
# Prerequisite warning: `pkcs11-tool` (OpenSC) is a hard requirement — the
# task fails loudly if it is missing, with no silent fallback. It does not
# need a separate install step here: shell.nix already adds `pkgs.opensc` to
# the nix-shell PATH on Linux whenever `WITH_HSM=1` is set, which this task
# sets before entering the nix shell (see .mise/tasks/test/hsm-pkcs11-tool).
- name: Test (PKCS#11 v3 conformance via pkcs11-tool)
if: matrix.hsm-type == 'softhsm2'
run: |
mise run test:hsm-pkcs11-tool --variant ${{ matrix.features }}

# Hardware HSMs reachable only with upstream-only secrets and infrastructure (Proteccio
# network HSM, Crypt2Pay over OpenVPN, the AWS CloudHSM CI cluster). They live in their
# own job (a job-level `if` cannot read the `matrix` context) and are skipped on forks.
hsm-upstream:
name: HSM ${{ matrix.hsm-type }} - ${{ matrix.features }}
runs-on: ubuntu-latest
if: github.repository == 'Cosmian/kms'
# This hardware does not support concurrent connections from multiple CI runs: a fixed
# group per HSM so only one job runs at a time across all PRs.
concurrency:
group: ${{ format('hsm-{0}', matrix.hsm-type) }}
queue: max
cancel-in-progress: false
strategy:
fail-fast: false
matrix:
hsm-type:
- proteccio
- crypt2pay
- aws-cloudhsm
features: [fips, non-fips]
exclude:
# parallel connections on proteccio is not supported
- hsm-type: proteccio
features: fips
# Not required - testing non-fips is sufficient
- hsm-type: crypt2pay
features: fips
# Not required until FIPS-mode compatibility is confirmed against the cluster's
# supported mechanism list - testing non-fips is sufficient for now
- hsm-type: aws-cloudhsm
features: fips
# non-fips only: parallel connections on proteccio are not supported, non-fips is
# sufficient for crypt2pay, and aws-cloudhsm FIPS-mode compatibility is not yet
# confirmed against the cluster's supported mechanism list.
features: [non-fips]

steps:
- uses: actions/checkout@v7
Expand All @@ -270,11 +366,13 @@ jobs:

- name: Test
env:
# HSM
# Proteccio
PROTECCIO_IP: ${{ secrets.PROTECCIO_IP }}
PROTECCIO_PASSWORD: ${{ secrets.PROTECCIO_PASSWORD }}
PROTECCIO_SLOT: ${{ secrets.PROTECCIO_SLOT }}
# Crypt2Pay (reached through OpenVPN)
CRYPT2PAY_PASSWORD: ${{ secrets.CRYPT2PAY_PASSWORD }}
OVPN_CONF: ${{ secrets.OVPN_CONF }}
# AWS CloudHSM: persistent CI cluster (see crate/hsm/aws_cloudhsm/README.md)
AWS_CLOUDHSM_CLUSTER_ID: ${{ secrets.KMS_CI_AWS_CLOUDHSM_CLUSTER_ID }}
AWS_CLOUDHSM_CU_USERNAME: ${{ secrets.KMS_CI_AWS_CLOUDHSM_CU_USERNAME }}
Expand All @@ -290,30 +388,9 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ secrets.KMS_CI_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.KMS_CI_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.KMS_CI_AWS_REGION }}
# Google variables
TEST_GOOGLE_OAUTH_CLIENT_ID: ${{ secrets.TEST_GOOGLE_OAUTH_CLIENT_ID }}
TEST_GOOGLE_OAUTH_CLIENT_SECRET: ${{ secrets.TEST_GOOGLE_OAUTH_CLIENT_SECRET }}
TEST_GOOGLE_OAUTH_REFRESH_TOKEN: ${{ secrets.TEST_GOOGLE_OAUTH_REFRESH_TOKEN }}
GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY }}
# OVPN for Crypt2Pay HSM tests
OVPN_CONF: ${{ secrets.OVPN_CONF }}
run: |
mise run test:hsm-${{ matrix.hsm-type }} --variant ${{ matrix.features }}

# PKCS#11 v3 conformance tests: signs through OpenSC's pkcs11-tool (a real,
# independently-implemented external PKCS#11 v3 client), not just our own
# in-process Rust tests above. SoftHSM2-only: it is the only backend the CI
# is allowed to reconfigure with an HSM-KEK for this check.
# Prerequisite warning: `pkcs11-tool` (OpenSC) is a hard requirement — the
# task fails loudly if it is missing, with no silent fallback. It does not
# need a separate install step here: shell.nix already adds `pkgs.opensc` to
# the nix-shell PATH on Linux whenever `WITH_HSM=1` is set, which this task
# sets before entering the nix shell (see .mise/tasks/test/hsm-pkcs11-tool).
- name: Test (PKCS#11 v3 conformance via pkcs11-tool)
if: matrix.hsm-type == 'softhsm2'
run: |
mise run test:hsm-pkcs11-tool --variant ${{ matrix.features }}

helm:
name: Helm chart E2E (minikube)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -351,7 +428,9 @@ jobs:
permissions:
contents: read
environment: xks-remote-approval
# Upstream only: needs the XKS test server and its secrets, which forks do not have.
if: >-
github.repository == 'Cosmian/kms' &&
github.actor != 'dependabot[bot]' &&
(github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository)
Expand Down Expand Up @@ -410,8 +489,13 @@ jobs:
cargo-publish:
needs:
- test-nix
- test-nix-upstream
- hsm
- hsm-upstream
- helm
# The upstream-only jobs are skipped on forks: a skipped dependency must not skip the
# publish dry-run, but any failed or cancelled dependency still blocks it.
if: ${{ !failure() && !cancelled() }}
uses: ./.github/workflows/cargo-publish.yml
with:
toolchain: 1.97.0
Expand Down
40 changes: 40 additions & 0 deletions CHANGELOG/claude_optimistic-carson-li1owh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# HSM delegation review fixes

## Security

- Reject SHA-1 RSA signatures in FIPS mode on the HSM-delegated pre-hashed path too: a
20-byte `digested_data` was signed as a SHA-1 PKCS#1 v1.5 `DigestInfo` through raw
`CKM_RSA_PKCS`, bypassing the FIPS gate that only blocked `CKM_SHA1_RSA_PKCS`

## Bug Fixes

### HSM

- Only infer the RSA PKCS#1 v1.5 hash from the input length when the input is a digest
(`digested_data`); a raw message with no explicit hash always signs with SHA-256 instead
of silently switching to SHA-1/SHA-384/SHA-512 for 20/48/64-byte messages
- Reject a `CryptographicAlgorithm` that does not match the key family (e.g. `ECDSA` on an
RSA key) with a clear KMIP error instead of an opaque PKCS#11 mechanism error
- Report a malformed DER ECDSA signature as invalid in HSM `SignatureVerify` instead of
returning an operation error
- Pool only read-write PKCS#11 sessions (`SlotManager::checkout_session` no longer takes an
ignored `read_write` flag)

### DB

- Keyset re-key eligibility and HSM latest-generation selection bypass the `RotateNameCache`,
so they are never decided on stale keyset state (e.g. a rotation done by another KMS node)
- `RotateNameCache` is invalidated by keyset name for all owners and generation filters, and
by member UID on update, state change (revoke/destroy), delete and HSM re-label; empty
results are no longer cached

### PKCS#11

- A signature cached by a `C_Sign` length query is only reused for the same data; a
follow-up call over different data is signed again instead of returning the earlier
signature

## Documentation

- Document the `RotateNameCache` invalidation and multi-node consistency model, and the
virtual-memory cost of the server's 16 MiB `RUST_MIN_STACK` default
Loading
Loading