eIDAS sim-crypto round-trip demo (TEST-ONLY)

Claim boundary: this is a TEST-ONLY simulation. It proves the EU/eIDAS plumbing (an eidas.sim sign → verify round-trip through the universal crypto simulator), not a certified eIDAS qualified electronic signature (QES). The simulator signs with deterministic HMAC-SHA256, not real eIDAS algorithms, has no QSCD/QTSP, and always reports production=false / testOnly=true / releaseEvidence=false. Do not present this as eIDAS-qualified.

This runbook gives a reproducible, isolated bring-up of the eIDAS sim-crypto round-trip plus the procedure for the full compliance-EU overlay. It is the deliverable of sprint SPRINT_20260530_006_DOCS_eidas_crypto_sim_demo (archived).

Audience: operators and engineers validating sovereign-crypto plumbing in a lab or air-gap environment. For the product framing of regional crypto packs, start at Assurance and Compliance Packs.

TL;DR — one command (isolated, does not touch the running stack)

bash devops/compose/scripts/eidas-sim-demo.sh

This builds the sim-crypto image, runs it as a throwaway standalone container (its own host port, not attached to the shared stellaops network, no stellaops-* container recreated), drives an eidas.sim sign → verify → tamper round-trip via the sim-crypto-smoke harness, then tears the container down.

Captured evidence (2026-05-30, lab):

[eidas-sim-demo] sim-crypto ready after 2s.
[eidas-sim-demo] Advertised /keys envelope:
{"provider":"sim.crypto.remote","keyId":"sim-key","algorithms":[...,"eidas.sim",...],
 "production":false,"testOnly":true,"releaseEvidence":false}
[eidas-sim-demo] Running eidas.sim round-trip via sim-crypto-smoke (SIM_PROFILE=eidas).
sim-crypto smoke passed: profile=eidas, algorithms=eidas.sim, url=http://localhost:18095
[eidas-sim-demo] eIDAS sim-crypto round-trip PASSED (test-only; production=false).

What’s actually there (no net-new code required)

All of this is real, building scaffolding — this demo wires and documents it:

PiecePathRole
Sim-crypto servicedevops/services/crypto/sim-crypto-service/ASP.NET minimal API: GET /keys, GET /health, POST /sign, POST /verify. Signs eidas.sim (and GOST/SM/PQ/FIPS/KCMVP labels) with HMAC-SHA256. Always emits production=false / testOnly=true / releaseEvidence=false.
Smoke / round-trip proverdevops/services/crypto/sim-crypto-smoke/Standalone harness: /keys → sign → verify(ok=true) → verify(tampered, ok=false), re-asserting the simulator envelope on every response. SIM_PROFILE=eidas targets eidas.sim.
.NET plugin (in-process)src/__Libraries/StellaOps.Cryptography.Plugin.SimRemote/SimRemoteProvider (sim.crypto.remote) — ICryptoProvider/ICryptoSigner that POSTs to the sim service’s /sign and /verify. Opt-in via STELLAOPS_ALLOW_UNSAFE_CRYPTO_SIM=1.
Plugin manifest entryetc/crypto-plugins-manifest.json (sim.crypto.remote)compliance:["test-only"], enabledByDefault:false, priority:10.
Crypto-sim compose overlaydevops/compose/docker-compose.crypto-provider.crypto-sim.ymlAdds the sim-crypto service + per-service STELLAOPS_CRYPTO_ENABLE_SIM=1. Refuses to start unless STELLAOPS_ALLOW_UNSAFE_CRYPTO_SIM=1.
EU compliance overlaydevops/compose/docker-compose.compliance-eu.ymlMounts etc/appsettings.crypto.eu.yaml, sets STELLAOPS_CRYPTO_PROFILE=eu, uses :eu-tagged images.
EU crypto profileetc/appsettings.crypto.eu.yamlSoft posture (see below).
EU env templatedevops/compose/env/compliance-eu.env.exampleSTELLAOPS_CRYPTO_PROFILE=eu, STELLAOPS_CRYPTO_ENABLE_SIM=1, STELLAOPS_ALLOW_UNSAFE_CRYPTO_SIM=1.

There is also a real (non-stub) EidasSoftCryptoProvider (src/__Libraries/StellaOps.Cryptography/CompliancePolicyCryptoProviders.cs, provider id eu.eidas.soft) that performs genuine ECDSA P-256/P-384 signing, gated behind EIDAS_SOFT_ALLOWED. It is software-only, non-certified, QSCD not enforced — it is a different lane from this simulator demo and is not what appsettings.crypto.eu.yaml currently enables (see the gap below).

How the round-trip is exercised (manual equivalent)

The helper script wraps these calls. To reproduce by hand against a running sim-crypto on host port 18095:

MSG=$(printf 'eidas-sim-demo:round-trip' | base64)

# Sign (algorithm = eidas.sim)
curl -s -X POST http://localhost:18095/sign -H 'Content-Type: application/json' \
  -d "{\"messageBase64\":\"$MSG\",\"algorithm\":\"eidas.sim\"}"
# -> {"signatureBase64":"...","algorithm":"eidas.sim","provider":"sim.crypto.remote",
#     "keyId":"sim-key","production":false,"testOnly":true,"releaseEvidence":false}

# Verify (correct message) -> {"ok":true, ...}
# Verify (tampered message) -> {"ok":false, ...}

Captured raw output (2026-05-30, lab):

SIGN   (eidas.sim): {"signatureBase64":"1gr3sFd4dXjhbVaE2HPI6c0fduQcsN7K31bNs3H/7jk=","algorithm":"eidas.sim","provider":"sim.crypto.remote","keyId":"sim-key","production":false,"testOnly":true,"releaseEvidence":false}
VERIFY (correct):   {"ok":true,"algorithm":"eidas.sim","provider":"sim.crypto.remote","keyId":"sim-key","production":false,"testOnly":true,"releaseEvidence":false}
VERIFY (tampered):  {"ok":false,"algorithm":"eidas.sim","provider":"sim.crypto.remote","keyId":"sim-key","production":false,"testOnly":true,"releaseEvidence":false}

The in-process plugin path is covered by src/__Libraries/StellaOps.Cryptography.Tests/SimRemoteProviderTests.cs (SignAndVerify_WithSimProvider_Succeeds, Supports_DefaultAlgorithms_CoversStandardIds), which pass (Total: 2, Failed: 0).

The full compliance-EU overlay bring-up (PROCEDURE — do NOT run against a shared/running stack)

The :eu-tagged images are not built by default. Bringing the EU overlay up against an already-running default stack would recreate the stellaops-* containers with the :eu images and break that environment. Use this only on a stack you own, or after building the :eu images. The TL;DR helper above is the safe, isolated path.

  1. Set the env (test-only opt-in is mandatory or the crypto-sim overlay refuses to start):

    cd devops/compose
    cp env/compliance-eu.env.example .env
    # .env already sets:
    #   STELLAOPS_CRYPTO_PROFILE=eu
    #   STELLAOPS_CRYPTO_ENABLE_SIM=1
    #   STELLAOPS_ALLOW_UNSAFE_CRYPTO_SIM=1
    #   STELLAOPS_CRYPTO_SIM_URL=http://sim-crypto:8080
    #   SIM_CRYPTO_PORT=18090
    
  2. Build the images (:eu service images + sim-crypto:dev):

    docker build -t registry.stella-ops.org/stellaops/sim-crypto:dev \
      ../services/crypto/sim-crypto-service
    # plus your :eu service images (build pipeline out of scope for this demo)
    
  3. Bring the stack up with both overlays:

    docker compose \
      -f docker-compose.stella-ops.yml \
      -f docker-compose.compliance-eu.yml \
      -f docker-compose.crypto-provider.crypto-sim.yml up -d
    
  4. Confirm stellaops-sim-crypto is healthy (GET /keys → 200) and the eIDAS-relevant services (Authority, Signer, Attestor, Scanner) received STELLAOPS_CRYPTO_ENABLE_SIM=1.

  5. Drive the round-trip against the in-network service:

    STELLAOPS_CRYPTO_SIM_URL=http://localhost:18090 SIM_PROFILE=eidas \
      dotnet run --project ../services/crypto/sim-crypto-smoke
    

Test-only posture and the open gap (read this)

eIDAS production-signing on-ramp (SPRINT_20260530_024 TASK-024-4)

The eu.eidas.soft provider already exists and is already reachable through the provider-selection routine — making it the production plug-in point for the eu profile requires no signer code change, only configuration. The real EidasSoftCryptoProvider (src/__Libraries/StellaOps.Cryptography/CompliancePolicyCryptoProviders.cs, provider id eu.eidas.soft) performs genuine ECDSA ES256/ES384 signing and is gated behind EIDAS_SOFT_ALLOWED.

How the routine selects it. Every compliance signer (capsule seal, SoA export, verdict attestation, regulatory payload) resolves its algorithm via SignatureProviderSelection.Resolve({Purpose, ProfileId, ...}) (src/__Libraries/StellaOps.Cryptography/SignatureProviderSelection.cs). Under the eidas compliance profile the routine resolves ES256 for every purpose, and the registry routes ES256 to whichever provider appears first in the active profile’s preferred order. So the on-ramp is purely:

  1. Compliance profile → eidas. Set Crypto:Compliance:ProfileId=eidas (env STELLAOPS_CRYPTO_COMPLIANCE_PROFILE=eidas) — drives the algorithm family to the eIDAS-acceptable ES256/ES384.
  2. Enable + prefer the provider. Set EIDAS_SOFT_ALLOWED=1 and put eu.eidas.soft first in StellaOps:Crypto:Registry:PreferredProviders (or pass it as the explicit SigningProviderHint/Provider override per signer). The EU compose overlay (docker-compose.compliance-eu.yml) carries the profile/gate plumbing.
  3. Provision the key on the provider. Pre-provision the signing key on eu.eidas.soft (Vault-wrapped or PKCS#11) and point the signer’s KeyId/Provider at it — exactly the KeyId/Provider/Algorithm config TASK-024-1/-3 use for the capsule + SoA signers. No raw private bytes in app config in production.

After those three settings, the capsule seal and SoA export sign with eu.eidas.soft ES256 instead of the lab default — a selection change, not a code change — and the published verification key (JWKS / SoA trust root) carries the eidas.soft public key.

soft → qualified (QES) upgrade path. eu.eidas.soft is software-only, non-certified (QSCD not enforced). Making it a qualified eIDAS seal (QES) is a separate, deliberate step and is intentionally not built speculatively (SPRINT_20260530_006 decision). To upgrade:

Until a customer requires QES, the eu profile signs with the soft eu.eidas.soft provider through the routine (production posture for the software lane) while the qualified posture stays an explicit, documented opt-in.

References