- Go 96.9%
- Shell 2.4%
- Makefile 0.7%
| .forgejo/workflows | ||
| .github | ||
| cmd/sepp | ||
| config | ||
| docs | ||
| e2e | ||
| e2etests | ||
| internal | ||
| patches | ||
| utils | ||
| .gitignore | ||
| config.yaml | ||
| CONTRIBUTING.md | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| makefile | ||
| README-BUILD.md | ||
| README.md | ||
| rockcraft.yaml | ||
| sepp | ||
| sepp.png | ||
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)
-
mTLS + X.509 (classic)
- TLS engine: Go crypto/tls (
gotls) - Mode:
x509-mtls - Port:
443(or any configured core port)
- TLS engine: Go crypto/tls (
Port 8443 (did / experimental)
-
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)
- TLS engine: wolfSSL (
Planned (not implemented yet)
-
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 (gotlsorwolfssl), and which modes are accepted on that listener.sepp.remote.urlorsepp.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 provideclient_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"]
Example 2: Dual-port (443 gotls + 8443 wolfssl), prefer didlink mode
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-mtlsover gotls/443) - B <-> C uses didlink (
didlink-x509-mtlsover 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.urlsmappings so the selected mode yields the correct base URL.
What’s 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>