kych

OAuth 2.0 API for Swiyu to enable Taler integration of Swiyu for KYC (experimental)
Log | Files | Refs | Submodules | README | LICENSE

taler-swiyu-verifier-manual.rst (41530B)


      1 ..
      2   This file is part of GNU TALER.
      3 
      4   Copyright (C) 2024, 2025 Taler Systems SA
      5 
      6   TALER is free software; you can redistribute it and/or modify it under the
      7   terms of the GNU Affero General Public License as published by the Free Software
      8   Foundation; either version 2.1, or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     11   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     12   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     13 
     14   You should have received a copy of the GNU Affero General Public License along with
     15   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     16 
     17 
     18 Swiyu Verifier Operator Manual
     19 ##############################
     20 
     21 Introduction
     22 ============
     23 
     24 About Swiyu Verifier
     25 --------------------
     26 
     27 The Swiyu Generic Verifier Service is a web server implementing the technical
     28 standards specified in the Swiyu Trust Infrastructure Interoperability Profile.
     29 It provides the capability to verify Verifiable Credentials held in users'
     30 digital wallets using the OpenID for Verifiable Presentations (OID4VP)
     31 protocol.
     32 
     33 The verifier service exposes two distinct API interfaces:
     34 
     35 - **Management API**: Internal interface for business systems to create
     36   verification requests and retrieve results. This API should only be
     37   accessible from within your organization's network.
     38 
     39 - **OID4VP API**: Public interface that implements the OpenID for Verifiable
     40   Presentations protocol. This interface must be accessible by wallet
     41   applications and requires HTTPS with a valid TLS certificate.
     42 
     43 When integrated with KyCH OAuth2 Gateway, the Swiyu Verifier enables
     44 KYC (Know Your Customer) verification flows for GNU Taler exchanges,
     45 allowing users to prove their identity attributes using Swiss Beta ID
     46 credentials stored in the Swiyu Wallet.
     47 
     48 
     49 About this manual
     50 -----------------
     51 
     52 This manual targets system administrators who want to install, configure,
     53 and operate a Swiyu Verifier service. It covers the onboarding process
     54 to the Swiyu Trust Infrastructure, service configuration, integration
     55 with KyCH OAuth2 Gateway, and API usage.
     56 
     57 
     58 Architecture overview
     59 ---------------------
     60 
     61 The following diagram illustrates the high-level architecture of credential
     62 verification using the Swiyu Verifier:
     63 
     64 ::
     65 
     66    +------------------+     +------------------+     +------------------+
     67    |                  |     |                  |     |                  |
     68    |  Business        |---->|  Swiyu           |<----|  Swiyu           |
     69    |  Verifier        |     |  Verifier        |     |  Wallet          |
     70    |  (e.g., KyCH)    |     |  Service         |     |                  |
     71    |                  |     |                  |     |                  |
     72    +------------------+     +------------------+     +------------------+
     73                                     |
     74                                     v
     75                            +------------------+
     76                            |                  |
     77                            |  Swiyu           |
     78                            |  Registries      |
     79                            |                  |
     80                            +------------------+
     81 
     82 The verification flow proceeds as follows:
     83 
     84 1. The business verifier (such as KyCH) creates a verification request
     85    via the Management API, specifying which credential claims to verify.
     86 
     87 2. The verifier service stores the request and returns a verification URI
     88    and optional deeplink for QR code generation.
     89 
     90 3. The user's wallet retrieves the verification request object via the
     91    OID4VP API and displays the requested claims for user consent.
     92 
     93 4. Upon user consent, the wallet sends the authorization response containing
     94    the Verifiable Presentation with the requested credential data.
     95 
     96 5. The verifier service validates the credential by checking the issuer's
     97    public key against the Base Registry and verifying the credential status
     98    against the Status Registry.
     99 
    100 6. The business verifier retrieves the verification result and disclosed
    101    claims either by polling or via webhook notification.
    102 
    103 
    104 Prerequisites
    105 =============
    106 
    107 System requirements
    108 -------------------
    109 
    110 Before deploying the Swiyu Verifier, ensure your system meets these requirements:
    111 
    112 - **Java Runtime Environment (JRE)**: Version 25 or higher. The verifier
    113   moved to Java 25 with its 5.0.0 release; releases up to 4.2.0 need Java 21.
    114 - **PostgreSQL**: Version 12 or higher
    115 - **Disk space**: Approximately 100 MB
    116 - **Network access**: Outbound access to Swiyu registries and inbound access
    117   for wallet connections
    118 - **Operating system**: Linux x64/AArch64, macOS AArch64, or Windows x64
    119 
    120 
    121 HTTPS requirement
    122 -----------------
    123 
    124 The Swiyu Wallet only accepts HTTPS connections with valid TLS certificates.
    125 Your verifier's OID4VP endpoints must be accessible via HTTPS using a
    126 certificate from a trusted Certificate Authority.
    127 
    128 For development and testing, you can use a tunneling service like ngrok
    129 to expose your local verifier with a valid HTTPS URL:
    130 
    131 .. code-block:: shell-session
    132 
    133    $ ngrok http 8080
    134 
    135 This provides a public HTTPS URL (e.g., ``https://abc123.ngrok-free.app``)
    136 that forwards to your local verifier. Use this URL as the ``external-url``
    137 in your configuration.
    138 
    139 For production deployments, obtain a proper TLS certificate and configure
    140 your reverse proxy or load balancer accordingly.
    141 
    142 
    143 Onboarding to Swiyu Trust Infrastructure
    144 ========================================
    145 
    146 Before operating a verifier in the Swiyu ecosystem, you must complete the
    147 onboarding process to register your service on the Base Registry. This
    148 section summarizes the required steps.
    149 
    150 The onboarding process involves several administrative and technical steps
    151 that establish your identity as a trusted verifier within the Swiyu Trust
    152 Infrastructure.
    153 
    154 .. note::
    155 
    156    The official Swiyu documentation provides detailed cookbooks that guide
    157    you through each step of the onboarding process:
    158 
    159    - `Onboarding the swiyu Base & Trust Registry <https://swiyu-admin-ch.github.io/cookbooks/onboarding-base-and-trust-registry/>`_:
    160      Complete guide for registering your organization and managing DIDs
    161 
    162    - `Getting started with the swiyu Generic Verifier <https://swiyu-admin-ch.github.io/cookbooks/onboarding-generic-verifier/>`_:
    163      Step-by-step deployment guide for the verifier service
    164 
    165    - `Designing the visualization for Issuer, Verifier and Credential <https://swiyu-admin-ch.github.io/cookbooks/vc-visual-presentation/>`_:
    166      How to configure the display metadata shown in wallets
    167 
    168    - `How to use Beta-ID for my business <https://swiyu-admin-ch.github.io/cookbooks/how-to-use-beta-id/>`_:
    169      Details about the Swiss Beta ID credential and available claims
    170 
    171    The following sections provide a summary. Refer to the official cookbooks
    172    for the most up-to-date and detailed instructions.
    173 
    174 
    175 Step 1: Access the Swiss Confederacy ePortal
    176 --------------------------------------------
    177 
    178 Sign in or create an account at the Swiss Confederacy ePortal using
    179 an AGOV or CH-Login account. This account is required for all
    180 subsequent registration steps.
    181 
    182 The ePortal is available at https://www.eportal.admin.ch/.
    183 
    184 
    185 Step 2: Register as Business Partner
    186 ------------------------------------
    187 
    188 Complete the business partner registration process through the ePortal.
    189 This establishes your organization's identity within the trust infrastructure.
    190 
    191 Navigate to the Swiyu Public Beta section and follow the business partner
    192 registration workflow.
    193 
    194 
    195 Step 3: Obtain API Keys
    196 -----------------------
    197 
    198 Access the API self-service portal to generate the API keys required for
    199 interacting with the Swiyu registries. These keys authenticate your
    200 verifier service when making registry requests.
    201 
    202 The self-service portal provides:
    203 
    204 - API credentials for the Base Registry (DID management)
    205 - API credentials for the Status Registry (credential revocation checks)
    206 
    207 
    208 Step 4: Allocate DID Space
    209 --------------------------
    210 
    211 Request allocation of a Decentralized Identifier (DID) space on the
    212 Swiyu Base Registry. This provides a unique namespace for your verifier's
    213 cryptographic identity.
    214 
    215 The DID follows the ``did:tdw`` method (Trust DID Web) with the format::
    216 
    217    did:tdw:<hash>:identifier-reg.trust-infra.swiyu.admin.ch:api:v1:did:<uuid>
    218 
    219 
    220 Step 5: Generate Cryptographic Keys
    221 -----------------------------------
    222 
    223 Use the Swiyu DID Toolbox to generate the required cryptographic keys
    224 and DID log. The toolbox creates:
    225 
    226 - An EC P-256 private key for signing verification requests
    227 - The corresponding DID Document entries
    228 - A DID log file for registration
    229 
    230 Download the DID Toolbox from the official Swiyu repository and run:
    231 
    232 .. code-block:: shell-session
    233 
    234    $ java -jar didtoolbox.jar generate
    235 
    236 This creates a ``.didtoolbox`` directory containing your keys and DID log.
    237 The key files include:
    238 
    239 - ``auth-key-01``: The private authentication key (keep this secure)
    240 - ``did-log.json``: The DID log to upload to the registry
    241 
    242 The generated DID log contains the values needed for verifier configuration:
    243 
    244 - ``value.id``: Your verifier's DID (used for ``client_id``)
    245 - ``value.assertionMethod``: The key reference (used for ``signing-key-verification-method``)
    246 
    247 
    248 Step 6: Upload DID Log
    249 ----------------------
    250 
    251 Upload the generated DID log to the Base Registry using the API keys
    252 obtained in Step 3. This publishes your verifier's DID Document,
    253 making your public key available for credential verification.
    254 
    255 Use the Base Registry API to upload your DID log::
    256 
    257    POST https://identifier-reg.trust-infra.swiyu.admin.ch/api/v1/did/<your-uuid>/log
    258 
    259 After successful upload, your DID is resolvable and wallets can verify
    260 your signed request objects.
    261 
    262 
    263 Step 7: (Optional) Become a Trusted Participant
    264 -----------------------------------------------
    265 
    266 Optionally register on the Trust Registry to become a trusted participant.
    267 This enables other parties to verify your verifier's trustworthiness
    268 through the trust infrastructure.
    269 
    270 See the `Onboarding the swiyu Base & Trust Registry cookbook
    271 <https://swiyu-admin-ch.github.io/cookbooks/onboarding-base-and-trust-registry/>`_
    272 for details on trust registry registration.
    273 
    274 
    275 Installation
    276 ============
    277 
    278 From the Debian package
    279 -----------------------
    280 
    281 On Debian, the verifier is packaged as ``swiyu-verifier``, built from the
    282 KyCH source package (which pins an upstream release in a submodule). It
    283 installs the service, a systemd unit, a configuration under
    284 ``/etc/swiyu-verifier`` and the ``swiyu-verifier-dbconfig`` tool:
    285 
    286 .. code-block:: shell-session
    287 
    288    $ sudo apt install ./swiyu-verifier_*.deb
    289    $ sudo swiyu-verifier-dbconfig
    290 
    291 The service ships disabled, because it cannot verify anything before it has
    292 a DID and a signing key of its own. See
    293 ``/usr/share/doc/swiyu-verifier/README.Debian`` and swiyu-verifier(1) for the
    294 remaining steps, and the `Configuration`_ section below for what the settings
    295 mean. The rest of this manual describes the service itself, and applies
    296 either way.
    297 
    298 
    299 Building from source
    300 --------------------
    301 
    302 Clone the Swiyu Verifier repository and build using Maven:
    303 
    304 .. code-block:: shell-session
    305 
    306    $ git clone https://github.com/swiyu-admin-ch/swiyu-verifier.git
    307    $ cd swiyu-verifier
    308    $ ./mvnw clean install -DskipTests
    309 
    310 This produces the executable JAR files in the ``verifier-application/target``
    311 directory. A JDK 25 is required; with an older default JDK on the host, point
    312 ``JAVA_HOME`` at it explicitly.
    313 
    314 
    315 Database setup
    316 --------------
    317 
    318 The Swiyu Verifier requires a PostgreSQL database to store verification
    319 requests and results. Create the database and user with appropriate
    320 privileges:
    321 
    322 .. code-block:: shell-session
    323 
    324    $ psql -U postgres
    325 
    326 .. code-block:: sql
    327 
    328    CREATE USER verifier_user WITH PASSWORD 'your_secure_password';
    329    CREATE DATABASE verifier_db OWNER verifier_user;
    330    GRANT ALL PRIVILEGES ON DATABASE verifier_db TO verifier_user;
    331 
    332 The ``CREATE USER`` command creates a PostgreSQL role that the verifier
    333 service will use to authenticate with the database. The ``CREATE DATABASE``
    334 command creates the database and assigns ownership to the verifier user.
    335 The ``GRANT`` command ensures the user has full access to the database.
    336 
    337 The verifier creates and migrates its own schema with Flyway on every start,
    338 so an empty database owned by its role is all it needs -- including after an
    339 upgrade, where there is nothing to do by hand.
    340 
    341 .. warning::
    342 
    343    Do not set ``spring.jpa.hibernate.ddl-auto`` to ``create`` or
    344    ``create-drop`` to achieve this. Those drop the schema, and with it every
    345    verification in the database, on each start. The correct value is
    346    ``validate``, which has Hibernate check the schema Flyway produced; it is
    347    the upstream default.
    348 
    349 
    350 Configuration
    351 =============
    352 
    353 The Swiyu Verifier uses Spring Boot's externalized configuration system,
    354 which supports YAML configuration files, environment variables, and
    355 profile-based overrides. This section explains the configuration profiles
    356 and how to use them for different deployment scenarios.
    357 
    358 
    359 Configuration profiles
    360 ----------------------
    361 
    362 Spring Boot profiles allow you to define environment-specific configurations
    363 that override the base ``application.yml`` settings. The verifier ships with
    364 several pre-configured profiles for different deployment scenarios.
    365 
    366 **Base configuration (application.yml)**
    367 
    368 The base configuration file defines all available settings with environment
    369 variable placeholders. This configuration is designed for production
    370 deployments where settings are injected via environment variables or
    371 container orchestration:
    372 
    373 .. code-block:: yaml
    374 
    375    application:
    376      external-url: "${EXTERNAL_URL:}"
    377      client_id: "${VERIFIER_DID:}"
    378      client_id_prefix: "decentralized_identifier"
    379      signing_key: "${secret.signing_key:${SIGNING_KEY:}}"
    380      signing-key-verification-method: "${DID_VERIFICATION_METHOD:}"
    381      client-metadata-file: "${OPENID_CLIENT_METADATA_FILE:}"
    382      deeplink-schema: ${DEEPLINK_SCHEMA:swiyu-verify}
    383      additional-audit-information:
    384        credential-subject-data-enabled: ${ADDITIONAL_AUDIT_INFORMATION_CREDENTIAL_SUBJECT_DATA_ENABLED:true}
    385 
    386    spring:
    387      datasource:
    388        url: "${POSTGRES_JDBC}"
    389        username: "${secret.db.username:${POSTGRES_USER}}"
    390        password: "${secret.db.password:${POSTGRES_PASSWORD}}"
    391      flyway:
    392        enabled: true
    393      jpa:
    394        hibernate:
    395          ddl-auto: validate
    396 
    397 The ``${VARIABLE:default}`` syntax means the value is read from the
    398 environment variable ``VARIABLE``, falling back to ``default`` if not set.
    399 The nested form ``${secret.value:${ENV_VAR:}}`` first checks for a
    400 mounted secret file, then an environment variable.
    401 
    402 **Local profile (application-local.yml)**
    403 
    404 The ``local`` profile is for development with Docker Compose managing the
    405 PostgreSQL database. When it is active, Spring Boot starts a PostgreSQL
    406 container from ``compose.yaml``, and the profile supplies a throwaway signing
    407 key and example DID values -- which are of course not valid for real
    408 verifications:
    409 
    410 .. code-block:: yaml
    411 
    412    application:
    413      signing_key: "-----BEGIN EC PRIVATE KEY-----\n...\n-----END EC PRIVATE KEY-----\n"
    414      signing-key-verification-method: "did:example:12345#key-1"
    415      external-url: "http://${server.host}:${server.port}"
    416      client-metadata-file: "classpath:client_metadata.json"
    417 
    418    spring:
    419      docker:
    420        compose:
    421          enabled: true
    422          file: compose.yaml
    423      datasource:
    424        url: "jdbc:postgresql://localhost:5434/verifier_db"
    425        username: "verifier_user"
    426        password: "secret"
    427      jpa:
    428        hibernate:
    429          ddl-auto: validate
    430 
    431 .. note::
    432 
    433    Earlier releases also shipped a ``local-dockerless`` profile. It is gone;
    434    for development against a PostgreSQL of your own, supply a configuration
    435    file instead of activating a profile, as the next section describes.
    436 
    437 
    438 **A configuration file of your own**
    439 
    440 Rather than editing the upstream tree or activating a profile, pass an extra
    441 configuration file, which Spring Boot reads after the one inside the jar and
    442 which therefore overrides it:
    443 
    444 .. code-block:: shell-session
    445 
    446    $ ./mvnw spring-boot:run -pl verifier-application \
    447        -Dspring-boot.run.jvmArguments=\
    448    "-Dspring.config.additional-location=file:/path/to/my-verifier.yml"
    449 
    450 with ``my-verifier.yml``:
    451 
    452 .. code-block:: yaml
    453 
    454    application:
    455      external-url: "https://your-ngrok-url.ngrok-free.app"
    456      client_id: "did:tdw:QmHash:identifier-reg.trust-infra.swiyu-int.admin.ch:api:v1:did:your-uuid"
    457      signing-key-verification-method: "did:tdw:QmHash:identifier-reg.trust-infra.swiyu-int.admin.ch:api:v1:did:your-uuid#auth-key-01"
    458      signing_key: |
    459        -----BEGIN EC PRIVATE KEY-----
    460        MHcCAQEEI...your_actual_key...
    461        -----END EC PRIVATE KEY-----
    462      client-metadata-file: "file:./client_metadata.json"
    463 
    464    spring:
    465      docker:
    466        compose:
    467          enabled: false
    468      datasource:
    469        url: "jdbc:postgresql://localhost:5432/verifier_db"
    470        username: "verifier_user"
    471        password: "secret"
    472      jpa:
    473        hibernate:
    474          ddl-auto: validate
    475 
    476    webhook:
    477      callback-uri: "https://kych.example.com/notification"
    478      callback-interval: 5000
    479 
    480 This is what ``swiyu-verifier/run.sh`` in the KyCH tree does, and what the
    481 Debian package does with ``/etc/swiyu-verifier/application.yml``.
    482 
    483 Note that ``client_id_scheme`` is no longer a setting: release 4.0.0 replaced
    484 it with ``client_id_prefix``, which defaults to ``decentralized_identifier``
    485 and is prepended to ``client_id`` in what the wallet receives.
    486 
    487 
    488 Environment variables
    489 ---------------------
    490 
    491 Every setting of the base configuration has an environment variable, which is
    492 how a container deployment and the Debian package both configure the service:
    493 
    494 .. code-block:: bash
    495 
    496    # Verifier identity
    497    EXTERNAL_URL="https://your-verifier.example.com"
    498    VERIFIER_DID="did:tdw:QmHash:identifier-reg.trust-infra.swiyu.admin.ch:api:v1:did:your-uuid"
    499    DID_VERIFICATION_METHOD="did:tdw:QmHash:identifier-reg.trust-infra.swiyu.admin.ch:api:v1:did:your-uuid#auth-key-01"
    500    OPENID_CLIENT_METADATA_FILE="file:/etc/swiyu-verifier/client_metadata.json"
    501 
    502    # Database connection
    503    POSTGRES_USER="swiyu_verifier"
    504    POSTGRES_PASSWORD="..."
    505    POSTGRES_JDBC="jdbc:postgresql://localhost:5432/swiyu_verifier"
    506 
    507 The signing key is the exception worth knowing about. It is a multi-line PEM
    508 document, so it cannot be carried in a systemd environment file, and an
    509 escaped ``\n`` in such a file reaches the JVM as two characters, which fails
    510 with ``No PEM key found``. Put it in a YAML file -- where a block scalar
    511 expresses it directly -- and have the service import it; the Debian package
    512 ships ``/usr/share/swiyu-verifier/signing-key.yml.example`` for exactly this
    513 and imports ``/etc/swiyu-verifier/secrets/signing-key.yml`` with
    514 ``spring.config.import``.
    515 
    516 
    517 Deployment Options
    518 ==================
    519 
    520 The Swiyu Verifier supports multiple deployment strategies depending on
    521 your infrastructure and requirements.
    522 
    523 
    524 Local development without Docker
    525 --------------------------------
    526 
    527 This approach runs the verifier directly on your machine with a locally
    528 installed PostgreSQL database.
    529 
    530 **1. Set up PostgreSQL**
    531 
    532 Create the database and user:
    533 
    534 .. code-block:: shell-session
    535 
    536    $ psql -d postgres -c "CREATE USER verifier_user WITH PASSWORD 'secret';"
    537    $ psql -d postgres -c "CREATE DATABASE verifier_db OWNER verifier_user;"
    538    $ psql -d verifier_db -c "GRANT ALL PRIVILEGES ON DATABASE verifier_db TO verifier_user;"
    539 
    540 **2. Write a configuration file**
    541 
    542 Put your DID, verification method and signing key from the Swiyu onboarding
    543 into a YAML file of your own, as "A configuration file of your own" above
    544 describes. (There is no ``local-dockerless`` profile any more.)
    545 
    546 **3. Start the verifier**
    547 
    548 .. code-block:: shell-session
    549 
    550    $ ./mvnw spring-boot:run -pl verifier-application \
    551        -Dspring-boot.run.jvmArguments=\
    552    "-Dspring.config.additional-location=file:/path/to/my-verifier.yml"
    553 
    554 **4. Expose via ngrok (for wallet testing)**
    555 
    556 Since wallets require HTTPS, use ngrok to expose your local verifier:
    557 
    558 .. code-block:: shell-session
    559 
    560    $ ngrok http 8080
    561 
    562 Update the ``external-url`` in your configuration with the ngrok URL.
    563 
    564 
    565 Local development with Docker Compose
    566 -------------------------------------
    567 
    568 This approach uses Docker Compose to manage the PostgreSQL database while
    569 running the verifier application directly.
    570 
    571 **1. Start the verifier with Docker Compose support**
    572 
    573 .. code-block:: shell-session
    574 
    575    $ ./mvnw spring-boot:run -pl verifier-application \
    576        -Dspring-boot.run.profiles=local
    577 
    578 Spring Boot automatically starts the PostgreSQL container defined in
    579 ``compose.yaml`` before the application starts.
    580 
    581 **2. Verify the database container**
    582 
    583 .. code-block:: shell-session
    584 
    585    $ docker ps
    586    CONTAINER ID   IMAGE              PORTS                    NAMES
    587    abc123def456   postgres:15-alpine 0.0.0.0:5434->5432/tcp   verifier_postgres
    588 
    589 The database is accessible on port 5434 to avoid conflicts with any
    590 existing PostgreSQL installation.
    591 
    592 
    593 Docker Compose full deployment
    594 ------------------------------
    595 
    596 For testing the complete stack in containers, use the sample compose file
    597 that includes both the verifier service and database:
    598 
    599 **1. Create a .env file with your credentials**
    600 
    601 .. code-block:: bash
    602 
    603    EXTERNAL_URL=https://your-verifier.example.com
    604    VERIFIER_DID=did:tdw:QmHash:identifier-reg.trust-infra.swiyu.admin.ch:api:v1:did:your-uuid
    605    DID_VERIFICATION_METHOD=did:tdw:QmHash:identifier-reg.trust-infra.swiyu.admin.ch:api:v1:did:your-uuid#auth-key-01
    606    SIGNING_KEY="-----BEGIN EC PRIVATE KEY-----
    607    ...your key...
    608    -----END EC PRIVATE KEY-----"
    609    OPENID_CLIENT_METADATA_FILE=/verifier_metadata.json
    610 
    611 **2. Start the stack**
    612 
    613 .. code-block:: shell-session
    614 
    615    $ docker compose -f sample.compose.yml up -d
    616 
    617 This starts:
    618 
    619 - ``verifier_postgres``: PostgreSQL 15 database on internal port 5432
    620 - ``verifier-service``: The verifier application on port 8083
    621 
    622 **3. Verify the deployment**
    623 
    624 .. code-block:: shell-session
    625 
    626    $ curl http://localhost:8083/swagger-ui/index.html
    627 
    628 The sample compose file:
    629 
    630 .. code-block:: yaml
    631 
    632    services:
    633      verifier_postgres:
    634        image: postgres:15-alpine
    635        environment:
    636          POSTGRES_USER: "verifier_user"
    637          POSTGRES_PASSWORD: "secret"
    638          POSTGRES_DB: "verifier_db"
    639        ports:
    640          - "5434:5432"
    641        healthcheck:
    642          test: ["CMD-SHELL", "pg_isready -U verifier_user -d verifier_db"]
    643          interval: 5s
    644          timeout: 5s
    645          retries: 5
    646 
    647      verifier-service:
    648        image: ghcr.io/swiyu-admin-ch/swiyu-verifier:latest
    649        configs:
    650          - source: verifier_metadata
    651            target: /verifier_metadata.json
    652        ports:
    653          - "8083:8080"
    654        environment:
    655          EXTERNAL_URL: ${EXTERNAL_URL}
    656          OPENID_CLIENT_METADATA_FILE: ${OPENID_CLIENT_METADATA_FILE}
    657          VERIFIER_DID: ${VERIFIER_DID}
    658          DID_VERIFICATION_METHOD: ${DID_VERIFICATION_METHOD}
    659          SIGNING_KEY: ${SIGNING_KEY}
    660          POSTGRES_USER: "verifier_user"
    661          POSTGRES_PASSWORD: "secret"
    662          POSTGRES_JDBC: "jdbc:postgresql://verifier_postgres:5432/verifier_db"
    663 
    664    configs:
    665      verifier_metadata:
    666        content: |
    667          {
    668            "client_id": "${VERIFIER_DID}",
    669            "client_name": "My Verifier",
    670            ...
    671          }
    672 
    673 
    674 Debug mode
    675 ----------
    676 
    677 To run the verifier with remote debugging enabled:
    678 
    679 .. code-block:: shell-session
    680 
    681    $ ./mvnw spring-boot:run -pl verifier-application \
    682        -Dspring-boot.run.fork=true \
    683        -Dspring-boot.run.jvmArguments="-Dspring.config.additional-location=file:/path/to/my-verifier.yml -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
    684 
    685 This starts the JVM debug agent on port 5005. Connect your IDE's remote
    686 debugger to ``localhost:5005``.
    687 
    688 
    689 Identity configuration
    690 ----------------------
    691 
    692 These settings establish your verifier's identity within the Swiyu
    693 Trust Infrastructure. All values are derived from the DID generation
    694 process during onboarding.
    695 
    696 ``application.client_id``
    697    Your verifier's DID as registered on the Base Registry. This uniquely
    698    identifies your service to wallets and other participants.
    699    Example: ``did:tdw:QmHash:identifier-reg.trust-infra.swiyu.admin.ch:api:v1:did:uuid``
    700 
    701 ``application.client_id_prefix``
    702    Prefix put in front of ``client_id`` in what the wallet receives, as in
    703    ``decentralized_identifier:did:tdw:...``. Defaults to
    704    ``decentralized_identifier`` and rarely needs changing. It replaced the
    705    ``client_id_scheme`` setting in release 4.0.0.
    706 
    707 ``application.signing-key-verification-method``
    708    The full DID with key fragment that identifies the public key in your
    709    DID Document. This tells wallets where to find the key for verifying
    710    your signed request objects.
    711    Example: ``did:tdw:QmHash:identifier-reg.trust-infra.swiyu.admin.ch:api:v1:did:uuid#auth-key-01``
    712 
    713 ``application.signing_key``
    714    The private EC key used to sign OID4VP request objects. This must be
    715    the private key corresponding to the public key published in your DID
    716    Document. Include the full PEM content including BEGIN and END markers::
    717 
    718       application:
    719         signing_key: |
    720           -----BEGIN EC PRIVATE KEY-----
    721           MHcCAQEEI...your_key_content...
    722           -----END EC PRIVATE KEY-----
    723 
    724 ``application.external-url``
    725    The publicly accessible URL of your verifier service. Wallets use this
    726    URL to retrieve request objects and submit presentations. Must be HTTPS
    727    in production.
    728    Example: ``https://verifier.example.com``
    729 
    730 ``application.client-metadata-file``
    731    Path to the verifier metadata JSON file. This file contains display
    732    information shown to users in their wallet, including localized names
    733    and logo.
    734    Example: ``classpath:/client_metadata.json``
    735 
    736 
    737 Database configuration
    738 ----------------------
    739 
    740 Configure the PostgreSQL connection for storing verification state:
    741 
    742 ``spring.datasource.url``
    743    JDBC connection URL for the PostgreSQL database.
    744    Example: ``jdbc:postgresql://localhost:5432/verifier_db``
    745 
    746 ``spring.datasource.username``
    747    Database username for authentication.
    748 
    749 ``spring.datasource.password``
    750    Database password for authentication.
    751 
    752 ``spring.jpa.hibernate.ddl-auto``
    753    Schema management strategy. Leave it at ``validate``, the upstream
    754    default: Flyway creates and migrates the schema (``spring.flyway.enabled``,
    755    also on by default), and ``validate`` has Hibernate check the result and
    756    refuse to start on a mismatch. ``create`` and ``create-drop`` drop the
    757    schema and every verification in it on each start, and ``update`` lets
    758    Hibernate make changes behind Flyway's back.
    759 
    760 
    761 Operational parameters
    762 ----------------------
    763 
    764 These settings control verification behavior and resource management:
    765 
    766 ``VERIFICATION_TTL_SEC``
    767    How long a verification request remains valid, in seconds. After this
    768    period, the verification expires and the user must start a new request.
    769    Default: ``900`` (15 minutes)
    770 
    771 ``DATA_CLEAR_PROCESS_INTERVAL_MS``
    772    Interval between cleanup runs that remove expired verifications from
    773    the database, in milliseconds.
    774    Default: ``420000`` (7 minutes)
    775 
    776 ``STATUS_LIST_CACHE_TTL_MILLI``
    777    How long to cache credential status list results, in milliseconds.
    778    Caching reduces load on the Status Registry.
    779    Default: ``900000`` (15 minutes)
    780 
    781 ``ISSUER_PUBLIC_KEY_CACHE_TTL_MILLI``
    782    How long to cache issuer public key lookups, in milliseconds.
    783    Caching reduces load on the Base Registry.
    784    Default: ``3600000`` (1 hour)
    785 
    786 
    787 Webhook configuration
    788 ---------------------
    789 
    790 Configure webhook callbacks to receive notifications when verification
    791 status changes, instead of polling:
    792 
    793 ``webhook.callback-uri``
    794    Full URL where webhook notifications should be sent. If not set,
    795    webhooks are disabled and clients must poll for status updates.
    796    Example: ``https://kych.example.com/notification``
    797 
    798 ``webhook.callback-interval``
    799    How often to send queued webhook notifications, in milliseconds.
    800    Failed deliveries are retried on subsequent intervals.
    801    Default: ``5000`` (5 seconds)
    802 
    803 ``WEBHOOK_API_KEY_HEADER``
    804    HTTP header name for API key authentication on the webhook endpoint.
    805    Optional but recommended for production.
    806    Example: ``X-API-Key``
    807 
    808 ``WEBHOOK_API_KEY_VALUE``
    809    The API key value to include in webhook requests. Required if
    810    ``WEBHOOK_API_KEY_HEADER`` is set.
    811 
    812 Webhook payloads contain:
    813 
    814 .. code-block:: json
    815 
    816    {
    817      "verification_id": "uuid-of-verification",
    818      "timestamp": "2025-01-23T10:30:00Z"
    819    }
    820 
    821 
    822 Security configuration
    823 ----------------------
    824 
    825 The Management API can be secured using OAuth 2.0 resource server
    826 authentication via Spring Security.
    827 
    828 For fixed public key authentication:
    829 
    830 ``SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_PUBLICKEYLOCATION``
    831    URI path to a public key in PEM format for JWT verification.
    832    Example: ``file:/etc/kych/management-api-public-key.pem``
    833 
    834 For dynamic key discovery via authorization server:
    835 
    836 ``SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURI``
    837    URI to the OAuth 2.0 authorization server. The verifier fetches
    838    the public key from the server's ``/.well-known/openid-configuration``.
    839 
    840 ``SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWKSETURI``
    841    Direct URI to the JWK Set endpoint, bypassing OpenID Connect discovery.
    842 
    843 ``SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWSALGORITHMS``
    844    Comma-separated list of accepted JWT signature algorithms.
    845    Default: ``RS256``
    846 
    847 
    848 Example configuration
    849 ---------------------
    850 
    851 Complete configuration example for production deployment:
    852 
    853 .. code-block:: yaml
    854 
    855    application:
    856      external-url: "https://verifier.example.com"
    857      client_id: "did:tdw:QmHash:identifier-reg.trust-infra.swiyu.admin.ch:api:v1:did:abc123"
    858      signing-key-verification-method: "did:tdw:QmHash:identifier-reg.trust-infra.swiyu.admin.ch:api:v1:did:abc123#auth-key-01"
    859      signing_key: |
    860        -----BEGIN EC PRIVATE KEY-----
    861        MHcCAQEEIABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstu
    862        oAoGCCqGSM49AwEHoUQDQgAE5cice...
    863        -----END EC PRIVATE KEY-----
    864      client-metadata-file: "classpath:/client_metadata.json"
    865 
    866    spring:
    867      datasource:
    868        driver-class-name: org.postgresql.Driver
    869        url: "jdbc:postgresql://localhost:5432/verifier_db"
    870        username: "verifier_user"
    871        password: "secure_password_here"
    872      jpa:
    873        hibernate:
    874          ddl-auto: validate
    875 
    876    webhook:
    877      callback-uri: "https://kych.example.com/notification"
    878      callback-interval: 5000
    879 
    880 
    881 Running the service
    882 ===================
    883 
    884 See the `Deployment Options`_ section for detailed instructions on running
    885 the verifier in different environments. This section covers verification
    886 and additional runtime configuration.
    887 
    888 
    889 Verifying the deployment
    890 ------------------------
    891 
    892 After starting, verify the service is running by accessing the Swagger UI:
    893 
    894 ``https://<your-external-url>/swagger-ui/index.html``
    895 
    896 The Management API endpoints are available under ``/management/api/``
    897 and the OID4VP endpoints under ``/oid4vp/api/``.
    898 
    899 
    900 Client metadata configuration
    901 -----------------------------
    902 
    903 The ``client_metadata.json`` file defines how your verifier appears to
    904 users in their wallet. Create this file with localized display information:
    905 
    906 .. code-block:: json
    907 
    908    {
    909      "client_id": "${VERIFIER_DID}",
    910      "client_name": "My Verifier Service",
    911      "client_name#en": "My Verifier Service",
    912      "client_name#de": "Mein Verifizierdienst",
    913      "client_name#fr": "Mon service de verification",
    914      "logo_uri": "https://example.com/logo.png",
    915      "vp_formats": {
    916        "jwt_vp": {
    917          "alg": ["ES256"]
    918        }
    919      }
    920    }
    921 
    922 The ``vp_formats`` field specifies which Verifiable Presentation formats
    923 your verifier accepts. For SD-JWT credentials, ``ES256`` is the standard
    924 algorithm.
    925 
    926 
    927 Integration with KyCH OAuth2 Gateway
    928 ====================================
    929 
    930 The Swiyu Verifier integrates with KyCH OAuth2 Gateway to provide KYC
    931 verification for GNU Taler exchanges. This section describes the
    932 required configuration on both sides.
    933 
    934 
    935 KyCH client configuration
    936 -------------------------
    937 
    938 In the KyCH configuration file, configure each client with the Swiyu
    939 Verifier URL and webhook endpoint:
    940 
    941 .. code-block:: ini
    942 
    943    [client_exchange]
    944    CLIENT_ID = taler-exchange
    945    CLIENT_SECRET = secure_secret
    946    VERIFIER_URL = https://verifier.example.com
    947    VERIFIER_MANAGEMENT_API_PATH = /management/api/verifications
    948    REDIRECT_URI = https://exchange.example.com/kyc/callback
    949    ACCEPTED_ISSUER_DIDS = {did:tdw:trusted_issuer_did}
    950 
    951 
    952 Webhook configuration
    953 ---------------------
    954 
    955 Configure the Swiyu Verifier to send status notifications to KyCH's
    956 notification endpoint:
    957 
    958 .. code-block:: yaml
    959 
    960    webhook:
    961      callback-uri: "https://kych.example.com/notification"
    962      callback-interval: 5000
    963 
    964 This allows KyCH to receive immediate notification when a verification
    965 completes, rather than relying on polling.
    966 
    967 
    968 Verification flow with KyCH
    969 ---------------------------
    970 
    971 When integrated with KyCH, the verification flow is:
    972 
    973 1. KyCH receives an OAuth 2.0 authorization request from the exchange.
    974 
    975 2. KyCH calls ``POST /management/api/verifications`` on the Swiyu Verifier
    976    with the credential claims specified in the OAuth scope.
    977 
    978 3. The verifier returns a verification URI and deeplink.
    979 
    980 4. KyCH displays a QR code to the user or provides the deeplink.
    981 
    982 5. The user's Swiyu Wallet scans the QR code and presents the credential.
    983 
    984 6. The Swiyu Verifier validates the credential and sends a webhook to KyCH.
    985 
    986 7. KyCH retrieves the verified claims and continues the OAuth flow.
    987 
    988 
    989 API Usage
    990 =========
    991 
    992 This section describes how to use the Swiyu Verifier APIs directly,
    993 which is useful for understanding the integration or for custom
    994 implementations.
    995 
    996 
    997 Creating a verification request
    998 -------------------------------
    999 
   1000 Create a verification request using the Management API:
   1001 
   1002 .. code-block:: console
   1003 
   1004    $ curl -X POST \
   1005        -H "Content-Type: application/json" \
   1006        -d @request.json \
   1007        https://verifier.example.com/management/api/verifications
   1008 
   1009 Where ``request.json`` contains:
   1010 
   1011 .. code-block:: json
   1012 
   1013    {
   1014      "accepted_issuer_dids": ["did:tdw:issuer_did"],
   1015      "jwt_secured_authorization_request": true,
   1016      "response_mode": "direct_post",
   1017      "dcql_query": {
   1018        "credentials": [{
   1019          "id": "age_check",
   1020          "format": "dc+sd-jwt",
   1021          "meta": { "vct_values": ["betaid-sdjwt"] },
   1022          "claims": [{ "path": ["age_over_18"] }],
   1023          "require_cryptographic_holder_binding": true
   1024        }]
   1025      }
   1026    }
   1027 
   1028 
   1029 Request parameters
   1030 ^^^^^^^^^^^^^^^^^^
   1031 
   1032 ``accepted_issuer_dids``
   1033    List of trusted issuer DIDs. Only credentials from these issuers are
   1034    accepted. If omitted, credentials from any issuer are accepted (not
   1035    recommended for production).
   1036 
   1037 ``trust_anchors``
   1038    Alternative to ``accepted_issuer_dids``. List of trust registry entry
   1039    DIDs. The verifier accepts credentials from any issuer trusted by
   1040    these anchors.
   1041 
   1042 ``dcql_query``
   1043    **Required.** What the wallet is asked to present, as a Digital
   1044    Credentials Query Language query. See the `DCQL query format`_ section
   1045    below.
   1046 
   1047    Release 3.0.0 dropped the DIF Presentation Exchange
   1048    ``presentation_definition`` that older versions took here, so a request
   1049    built for one of those is rejected.
   1050 
   1051 ``jwt_secured_authorization_request``
   1052    Whether to sign the request object. Default: ``true`` (recommended).
   1053 
   1054 ``response_mode``
   1055    How the wallet sends the response. Options:
   1056 
   1057    - ``direct_post``: Unencrypted response (current default)
   1058    - ``direct_post.jwt``: JWE-encrypted response (recommended for privacy)
   1059 
   1060 
   1061 Response
   1062 ^^^^^^^^
   1063 
   1064 .. code-block:: json
   1065 
   1066    {
   1067      "id": "verification-uuid",
   1068      "request_nonce": "random-nonce",
   1069      "state": "PENDING",
   1070      "verification_url": "https://verifier.example.com/oid4vp/api/request-object/request-id",
   1071      "verification_deeplink": "swiyu-verify://?client_id=..."
   1072    }
   1073 
   1074 Store the ``id`` for polling status. Use ``verification_deeplink`` to
   1075 generate a QR code for the user to scan.
   1076 
   1077 
   1078 Polling verification status
   1079 ---------------------------
   1080 
   1081 Check the status of a verification:
   1082 
   1083 .. code-block:: shell-session
   1084 
   1085    $ curl -X GET \
   1086        https://verifier.example.com/management/api/verifications/{verification_id}
   1087 
   1088 The response includes the current ``state``:
   1089 
   1090 - ``PENDING``: Waiting for user to present credentials
   1091 - ``SUCCESS``: Verification completed successfully
   1092 - ``FAILED``: Verification failed (see ``error_code``)
   1093 - ``REJECTED``: User rejected the verification request
   1094 - ``EXPIRED``: Verification request timed out
   1095 
   1096 
   1097 DCQL query format
   1098 -----------------
   1099 
   1100 The ``dcql_query`` follows the Digital Credentials Query Language of OpenID4VP
   1101 1.0, section 6. Each entry of ``credentials`` asks for one credential:
   1102 
   1103 ``id``
   1104    Names this credential query. The verifier reports the disclosed claims
   1105    back under this key in ``wallet_response.dcql_response``, so it is worth
   1106    choosing something recognisable; it must match ``^[a-zA-Z0-9_-]+$``.
   1107 
   1108 ``format``
   1109    ``dc+sd-jwt``, the canonical SD-JWT VC media type. The older
   1110    ``vc+sd-jwt`` is still accepted during the migration window.
   1111 
   1112 ``meta.vct_values``
   1113    The credential types that satisfy this query, matched against the ``vct``
   1114    claim of the presented credential -- ``betaid-sdjwt`` for the Swiss Beta
   1115    ID. This is what lets a wallet holding several credentials tell which one
   1116    is being asked for.
   1117 
   1118 ``claims``
   1119    The claims to disclose, one entry per claim, each a *claims path pointer*:
   1120    ``{"path": ["age_over_18"]}`` for a top-level claim, and
   1121    ``{"path": ["address", "street_address"]}`` for a nested one. Every claim
   1122    of the Beta ID is top-level. Leave the member out entirely rather than
   1123    sending an empty array, which is rejected.
   1124 
   1125 ``require_cryptographic_holder_binding``
   1126    Whether the wallet must prove it holds the credential's key. Defaults to
   1127    true, and there is no good reason to turn it off.
   1128 
   1129 ``trusted_authorities``
   1130    Optional, and normally not sent: when ``accepted_issuer_dids`` is given on
   1131    the request, the verifier fills this in from it, so that the wallet can
   1132    pre-filter the credentials it offers the user. If you do send it, it has to
   1133    agree with ``accepted_issuer_dids``.
   1134 
   1135 KyCH builds exactly such a query from the OAuth 2.0 scope, one claims query
   1136 per scope token; ``swiyu-verifier/api_requests/request_over_18.json`` in the
   1137 KyCH tree is a complete example in the same shape.
   1138 
   1139 
   1140 Error codes
   1141 ===========
   1142 
   1143 When verification fails, the response includes an error code explaining
   1144 the failure:
   1145 
   1146 ``credential_invalid``
   1147    Generic credential validation failure.
   1148 
   1149 ``credential_expired``
   1150    The presented credential has expired.
   1151 
   1152 ``credential_revoked``
   1153    The credential has been revoked by the issuer.
   1154 
   1155 ``credential_suspended``
   1156    The credential is temporarily suspended.
   1157 
   1158 ``credential_missing_data``
   1159    The credential does not contain the requested claims.
   1160 
   1161 ``issuer_not_accepted``
   1162    The credential issuer is not in the accepted issuers list.
   1163 
   1164 ``public_key_of_issuer_unresolvable``
   1165    Could not retrieve the issuer's public key from the Base Registry.
   1166 
   1167 ``unresolvable_status_list``
   1168    Could not check credential revocation status.
   1169 
   1170 ``holder_binding_mismatch``
   1171    The holder could not prove control of the credential.
   1172 
   1173 ``client_rejected``
   1174    The user rejected the verification request in their wallet.
   1175 
   1176 ``jwt_expired``
   1177    A JWT in the verification process has expired.
   1178 
   1179 ``invalid_format``
   1180    The credential or presentation format is invalid.
   1181 
   1182 
   1183 Deployment considerations
   1184 =========================
   1185 
   1186 Production security
   1187 -------------------
   1188 
   1189 For production deployments:
   1190 
   1191 1. **Protect the Management API**: Use network segmentation or OAuth 2.0
   1192    authentication to restrict access to internal systems only.
   1193 
   1194 2. **Use an API Gateway**: Place a WAF or API gateway in front of the
   1195    OID4VP endpoints to filter malicious requests.
   1196 
   1197 3. **Enable HTTPS**: The OID4VP endpoints must use HTTPS with a valid
   1198    certificate from a trusted CA.
   1199 
   1200 4. **Secure credentials**: Store the signing key securely. Consider using
   1201    an HSM for production key management.
   1202 
   1203 5. **Set accepted issuers**: Always specify ``accepted_issuer_dids`` or
   1204    ``trust_anchors`` in verification requests.
   1205 
   1206 
   1207 HSM support
   1208 -----------
   1209 
   1210 For production deployments, the signing key can be stored in a Hardware
   1211 Security Module (HSM) instead of the configuration file. Configure:
   1212 
   1213 ``SIGNING_KEY_MANAGEMENT_METHOD``
   1214    Set to ``pkcs11`` for Sun PKCS#11 provider or a vendor-specific value.
   1215 
   1216 ``HSM_HOST``, ``HSM_PORT``, ``HSM_USER``, ``HSM_PASSWORD``
   1217    Connection parameters for the HSM.
   1218 
   1219 ``HSM_KEY_ID``
   1220    Key identifier or alias within the HSM.
   1221 
   1222 See the Swiyu Verifier documentation for vendor-specific HSM configuration.
   1223 
   1224 
   1225 Monitoring
   1226 ----------
   1227 
   1228 The verifier exposes Prometheus metrics at ``/actuator/prometheus``.
   1229 Enable authentication for this endpoint in production:
   1230 
   1231 .. code-block:: yaml
   1232 
   1233    MONITORING_BASIC_AUTH_ENABLED: true
   1234    MONITORING_BASIC_AUTH_USERNAME: prometheus
   1235    MONITORING_BASIC_AUTH_PASSWORD: secure_password
   1236 
   1237 
   1238 See also
   1239 ========
   1240 
   1241 - kych.conf(5) - KyCH OAuth2 Gateway configuration
   1242 - kych-oauth2-gateway(1) - KyCH OAuth2 Gateway server
   1243 - `Swiyu Trust Infrastructure <https://swiyu-admin-ch.github.io/>`_
   1244 - `OID4VP Specification <https://openid.net/specs/openid-4-verifiable-presentations-1_0.html>`_
   1245 - `DCQL, in OpenID for Verifiable Presentations 1.0 <https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#section-6>`_
   1246 
   1247 
   1248 Bugs
   1249 ====
   1250 
   1251 Report bugs by using https://bugs.taler.net/ or by sending electronic
   1252 mail to <taler@gnu.org>.