EllaSEPP code written in GO. SEPP with DID management (resolve) and TLS-DID connection establishment. In parallel to x.509/TLS connections
  • Go 96.9%
  • Shell 2.4%
  • Makefile 0.7%
Find a file
Dirk 72b28b516f
Some checks failed
Go CI / test (push) Failing after 1s
Go CI / lint (push) Failing after 0s
Go CI / build (push) Failing after 0s
ci / build + test (gotls) (push) Failing after 0s
ci / build + test + e2e (wolfssl) (push) Failing after 1s
Update readme - how to kick off tests.
2026-02-11 15:35:55 +01:00
.forgejo/workflows ci(forgejo): add workflow to build + test gotls and wolfssl (incl. e2e) 2026-01-31 22:39:18 +01:00
.github chore(deps): bump docker/login-action from 3.1.0 to 3.2.0 (#18) 2024-05-29 05:52:46 -04:00
cmd/sepp feat(l7): wire sidecar into router and enable l7 negotiation/advertise 2026-02-11 15:10:25 +01:00
config feat(l7): add sidecar outbound transport and router support (unwired) 2026-02-11 00:18:55 +01:00
docs clean up2 2026-02-10 22:53:22 +01:00
e2e test(ui-wireframe): emit run artifacts for DIDLink negative E2E (events/logs/assertions 2026-02-03 18:23:55 +01:00
e2etests chore: Move e2e setup to test file (#12) 2024-02-06 08:35:32 -05:00
internal feat(l7): wire sidecar into router and enable l7 negotiation/advertise 2026-02-11 15:10:25 +01:00
patches chore(test): disable Go test caching and make e2e optional 2026-02-11 15:33:20 +01:00
utils docs: fix PlantUML sepp-component diagram syntax 2026-02-02 23:24:50 +01:00
.gitignore chore(patches): allow committing runner+lib while ignoring patch scripts 2026-02-10 23:57:57 +01:00
config.yaml clean up2 2026-02-10 22:53:22 +01:00
CONTRIBUTING.md feat: add rock (#7) 2024-02-01 14:33:22 -05:00
go.mod add SBI reverse-proxy test using env-gated sepp. 2026-01-28 23:19:04 +01:00
go.sum add SBI reverse-proxy test using env-gated sepp. 2026-01-28 23:19:04 +01:00
LICENSE feat: Implement POST Exchange Capability (#1) 2024-01-27 08:42:57 -05:00
makefile docs: fix PlantUML sepp-component diagram syntax 2026-02-02 23:24:50 +01:00
README-BUILD.md Update readme - how to kick off tests. 2026-02-11 15:35:55 +01:00
README.md overwrites original README.md 2026-02-02 23:44:33 +01:00
rockcraft.yaml chore: Add e2e tests (#11) 2024-02-05 18:32:59 -05:00
sepp clean up2 2026-02-10 22:53:22 +01:00
sepp.png feat: Add sepp client (#6) 2024-01-30 15:43:56 -05:00

This implementation builds upon Ellanetwork's open source SEPP under Apache 2.0 licence. Available as public archive at https://github.com/ellanetworks/sepp

SEPP General Overview

This repo runs SEPP instances that communicate via:

  • N32 (control / handshake) over HTTP/2
  • SBI (service plane) via HTTP reverse proxy over HTTP/2

Key goals:

  • run multiple SEPP instances (different IPs/ports)
  • support multiple TLS engines and “security profiles”
  • dual-port operation to separate TLS stacks by port

Terminology

A better word than “communication suite” is typically:

  • security profile (recommended)
  • TLS profile
  • transport security mode

In this repo we use “mode”/“profile” in configuration.


Supported ports & security profiles

We use different ports when different TLS engines are required.

Port 443 (core)

  1. mTLS + X.509 (classic)

    • TLS engine: Go crypto/tls (gotls)
    • Mode: x509-mtls
    • Port: 443 (or any configured core port)

Port 8443 (did / experimental)

  1. mTLS + X.509 using wolfSSL engine (foundation for DIDLink/RPK)

    • TLS engine: wolfSSL (wolfssl)
    • Mode: didlink-x509-mtls (currently uses X.509 mTLS transport; DID-binding logic is planned/next step)
    • Port: 8443 (or any configured did port)

Planned (not implemented yet)

  1. RPK (RFC7250) + DID binding

    • TLS engine: wolfSSL
    • Mode: did-rpk-mtls (name suggestion)
    • Requires: RPK handshake + DID verification/binding callbacks

Important: Today, “didlink-x509-mtls” selects a different port/backend and runs over X.509 mTLS. The actual DID binding / RPK verification steps are intended as follow-up work.


High-level architecture

flowchart LR
  subgraph Fabric["Hyperledger Fabric"]
    PeerA["Peer A\n(DID registry chaincode)"]
    PeerB["Peer B\n(DID registry chaincode)"]
  end

  subgraph SEPP_A["SEPP A"]
    N32A["N32 Server/Client\n(HTTP/2)"]
    SBIA["SBI Server\nReverse Proxy (HTTP/2)"]
    ResolverA["DID Resolver\n(BLOCKOTUS/did)"]
    TLSA["TLS Backend Registry\n- gotls\n- wolfssl"]
  end

  subgraph SEPP_B["SEPP B"]
    N32B["N32 Server/Client\n(HTTP/2)"]
    SBIB["SBI Server\nReverse Proxy (HTTP/2)"]
    ResolverB["DID Resolver\n(BLOCKOTUS/did)"]
    TLSB["TLS Backend Registry\n- gotls\n- wolfssl"]
  end

  PeerA <--> PeerB
  ResolverA <--> Fabric
  ResolverB <--> Fabric

  N32A <-- "HTTP/2 (TLS)" --> N32B
  SBIA <-- "HTTP/2 (TLS)" --> SBIB

  N32A --- TLSA
  SBIA --- TLSA
  N32B --- TLSB
  SBIB --- TLSB

Runtime message flows (N32 + SBI)

sequenceDiagram
  autonumber
  participant A_N32C as SEPP A - N32 Client
  participant B_N32S as SEPP B - N32 Server
  participant A_CTX as A Context/Router
  participant B_CTX as B Context/Router
  participant A_SBI as SEPP A - SBI ReverseProxy
  participant B_SBI as SEPP B - SBI Server

  Note over A_N32C,B_N32S: N32-C / N32-F capability exchange (HTTP/2 + mTLS)
  A_N32C->>B_N32S: POST /n32c-handshake/v1/exchange-capability
  B_N32S->>B_CTX: validate + select mode
  B_CTX-->>B_N32S: selected mode + sender FQDN
  B_N32S-->>A_N32C: 200 OK (SelectedSecCapability)

  A_N32C->>A_CTX: store remote FQDN + selected mode
  B_N32S->>B_CTX: store remote FQDN + selected mode

  Note over A_SBI,B_SBI: SBI traffic is proxied after N32 established peer info
  A_SBI->>A_CTX: lookup remote base URL (by selected mode)
  A_SBI->>B_SBI: forward request (HTTP/2 + TLS backend)
  B_SBI-->>A_SBI: response

Configuration overview

Configuration concepts used in examples:

  • sepp.local.listeners[] Defines inbound ports, backend engine (gotls or wolfssl), and which modes are accepted on that listener.
  • sepp.remote.url or sepp.remote.urls{ mode: url } Defines remote peer base URL(s) by mode.
  • sepp.peers[] (peer profiles) Optional: force outbound backend/modes for certain peers and provide client_tls.

Example 1: Two SEPPs, classic only (single-port)

SEPP A (core only, gotls):

sepp:
  securityCapability: "TLS"
  local:
    listeners:
      - name: core
        address: "0.0.0.0:443"
        backend: "gotls"
        advertise_url: "https://sepp-a.example:443"
        allowed_modes: ["x509-mtls"]
        tls:
          cert: "certs/a-server.pem"
          key:  "certs/a-server.key"
          ca:   "certs/ca.pem"
  remote:
    url: "https://sepp-b.example:443"
    tls:
      cert: "certs/a-client.pem"
      key:  "certs/a-client.key"
      ca:   "certs/ca.pem"

Component view:

flowchart LR
  A["SEPP A\n:443 gotls\nx509-mtls"] <-- "HTTP/2 + mTLS" --> B["SEPP B\n:443 gotls\nx509-mtls"]

SEPP A (dual-port):

sepp:
  securityCapability: "TLS"
  peers:
    - name: "sepp-b"
      base_url: "https://sepp-b.example"
      backend: "wolfssl"
      allowed_modes: ["didlink-x509-mtls"]
      client_tls:
        cert: "certs/a-client.pem"
        key:  "certs/a-client.key"
        ca:   "certs/ca.pem"

  local:
    listeners:
      - name: core
        address: "0.0.0.0:443"
        backend: "gotls"
        advertise_url: "https://sepp-a.example:443"
        allowed_modes: ["x509-mtls"]
        tls: { cert: "certs/a-server.pem", key: "certs/a-server.key", ca: "certs/ca.pem" }

      - name: did
        address: "0.0.0.0:8443"
        backend: "wolfssl"
        advertise_url: "https://sepp-a.example:8443"
        allowed_modes: ["didlink-x509-mtls"]
        tls: { cert: "certs/a-server.pem", key: "certs/a-server.key", ca: "certs/ca.pem" }

  remote:
    urls:
      x509-mtls: "https://sepp-b.example:443"
      didlink-x509-mtls: "https://sepp-b.example:8443"
    tls:
      cert: "certs/a-client.pem"
      key:  "certs/a-client.key"
      ca:   "certs/ca.pem"

Component view:

flowchart LR
  A443["SEPP A\n:443 gotls\nx509-mtls"] <-- "HTTP/2 + mTLS" --> B443["SEPP B\n:443 gotls\nx509-mtls"]
  A8443["SEPP A\n:8443 wolfssl\ndidlink-x509-mtls"] <-- "HTTP/2 + mTLS" --> B8443["SEPP B\n:8443 wolfssl\ndidlink-x509-mtls"]

Example 3: Three SEPPs in a chain with different profiles

Scenario:

  • A <-> B uses classic (x509-mtls over gotls/443)
  • B <-> C uses didlink (didlink-x509-mtls over wolfssl/8443)

Component view:

flowchart LR
  A["SEPP A\n:443 gotls\nx509-mtls"] <-- "HTTP/2 + mTLS" --> B1["SEPP B\n:443 gotls\nx509-mtls"]
  B2["SEPP B\n:8443 wolfssl\ndidlink-x509-mtls"] <-- "HTTP/2 + mTLS" --> C["SEPP C\n:8443 wolfssl\ndidlink-x509-mtls"]

Configuration guidance:

  • On B, run dual-port listeners.
  • Create peer profiles in B that select backend/modes per remote (A vs C).
  • Use remote.urls mappings so the selected mode yields the correct base URL.

Whats missing / next steps

For DIDLink as described in the paper, the transport is not only “TLS with different certs”. You will need:

  • a DID exchange mechanism (or DID discovery/lookup policy)
  • DID document resolution (Fabric-based) and key material extraction
  • binding rules (what exactly must match: endpoint, key id, proof, etc.)
  • for RPK mode: RFC7250 handshake + mapping to DID keys + verification callbacks

The current codebase provides the wiring points:

  • dual listeners (core vs did)
  • backend registry (gotls vs wolfssl)
  • per-peer routing/mode selection
  • E2E tests with two SEPP processes

Security reality check

Today the mode didlink-x509-mtls is primarily a routing / port / TLS-engine selector:

  • it chooses the did-port and the wolfSSL backend for a given peer/mode,
  • it still uses X.509 mTLS as the transport security mechanism.

What is not implemented yet (and should not be assumed):

  • cryptographic DID binding rules (e.g. verifying that a presented key/cert is bound to a resolved DID document),
  • a concrete DID exchange/discovery policy between peers,
  • RFC7250 Raw Public Keys handshake and verification callbacks.

Once DID binding / RPK are implemented, this section should be updated to clearly state which checks are enforced at which layer (TLS handshake vs. application-lev>