This project is a minimal end-to-end demo that shows how to integrate the Swisscom Trust Services Multiple Authentication Broker (MAB) APIs and the ETSI Sign API.
It is built as a reference implementation for:
- Using mTLS for all protected endpoints
- Calling OIDC + PAR
- Calling OIDC + CIBA
- Exchanging the authorization code for an access token (SAD JWT)
- Polling CIBA token endpoint for an access token (SAD JWT)
- Calling the ETSI signDoc endpoint to sign a document digest
- Providing a small browser UI for manual testing
- Preparing a PDF for signature with PDFBox
- Embedding the returned CMS signature back into the PDF to get a final, signed PDF without modifying the signed byte ranges
- Adding PAdES DSS/VRI structures to upgrade the signature to Baseline B-LT (LTV enabled)
- Java 21
- Spring Boot (WebFlux)
- Project Reactor (Mono / reactive flows)
- Netty HTTP client with mTLS (ReactorClientHttpConnector)
- Apache PDFBox
- External PDF signing
- CMS embedding into PDF signature placeholders
- Incremental update handling
- Manual construction of PAdES DSS + VRI structures
- PAdES Baseline B-LT (LTV enabled) support
- OpenAPI-generated clients (MAB + ETSI)
- Static HTML/CSS/JavaScript UI
- Minimal browser UI for manual end-to-end testing
- Copy/paste Authorization Code flow
To run this demo against Swisscom preprod you need:
- mTLS client certificate + key issued by STS
- client_id and client_secret issued by STS
- A configured redirect URL (either back to this UI or to an inspection endpoint for manual copy/paste)
This project is safe for public repositories and does not contain real secrets.
All configuration parameters are present in src/main/resources/application.yaml. They can be injected with environment variables or overridden for local development.
All configuration parameters in src/main/resources/application.yaml can be overridden in application-dev.yaml for local development.
Sample application-dev.yaml:
qtsp:
oidc:
discovery-path: https://example.invalid/.well-known/openid-configuration
client:
client-id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
client-secret: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
redirect-uri: https://webhook.site/<your-id>
mtls:
base-url: https://example.invalid
client-cert: classpath:test-client.pem
client-key: classpath:test-client.keyWarning: Never commit your
application-dev.yamlfile to any repository. This file may contain sensitive credentials or configuration values intended only for your local environment.
For local development, the app also loads a project-root .env file automatically at startup via Spring Boot config import. The .env file uses plain KEY=value entries, so you can keep local settings in one place instead of exporting them manually in every shell.
Create a .env file in the project root. You can start from the checked-in example:
cp .env.example .envSample .env:
DISCOVERY_PATH=https://example.invalid/.well-known/openid-configuration
CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
CLIENT_SECRET=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
REDIRECT_URI=https://webhook.site/<your-id>
MTLS_BASE_URL=https://example.invalid
MTLS_CLIENT_CERT=file:/absolute/path/to/client-cert.pem
MTLS_CLIENT_KEY=file:/absolute/path/to/client-key.pemFor MTLS_CLIENT_CERT and MTLS_CLIENT_KEY, use Spring resource syntax such as file:/absolute/path/to/client-cert.pem or classpath:....
Warning: Never commit your
.envfile to any repository. This file may contain sensitive credentials or configuration values intended only for your local environment.
If you prefer, you can also export the variables manually before starting the app.
If a variable is defined both in the .env file and as an exported OS environment variable, the OS environment variable takes precedence. This allows you to keep local defaults in .env while still being able to override them per shell or CI job.
export DISCOVERY_PATH="https://example.invalid/.well-known/openid-configuration"
export CLIENT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export CLIENT_SECRET="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export REDIRECT_URI="https://webhook.site/<your-id>"
export MTLS_BASE_URL="https://example.invalid"
export MTLS_CLIENT_CERT="file:/absolute/path/to/client-cert.pem"
export MTLS_CLIENT_KEY="file:/absolute/path/to/client-key.pem"The project can be built and run with Gradle. If you prefer shorter, more general commands, the project also supports Makefile.
./gradlew build./gradlew test./gradlew bootRunmakeThis runs ./gradlew bootRun via the project Makefile.
If you are using .env, no extra export step is required before running that command.
Additional shortcuts:
make build # ./gradlew buildmake test # ./gradlew testmake clean # ./gradlew cleanServer starts on:
Open the UI in a browser:
When running locally, be aware that localhost cannot be used as the redirect URI due to STS restrictions.
- (Quick and easy)
- When the MAB answers with a redirect URI containing the access code, you can copy and paste the code manually from the browser tab for testing purposes (easiest for quick tests).
- (More work but convenient)
- Use a local tunneling service such as ngrok or localtunnel to expose your local server to the internet. But mind security restrictions of such tunneling services in an enterprise context!
Example with ngrok:
ngrok http 8081Set your OAuth2 redirect URI to the public ngrok URL (e.g., https://randomid.ngrok.io/callback).
This demo is designed so it can be published safely:
- No real QTSP credentials are committed
- mTLS key material in the repository is demo-only
- Tokens are redacted in responses
- Session state is stored only in-memory (not production-safe)
Do not use this code as production signing software.
Use it as a reference integration blueprint.

