Sealed transport
A node speaks plain HTTP, and any TLS in front of it ends outside the enclave,
at whatever proxy or relay the operator runs. Everything crossing that hop —
the bearer token, a warrant, JSON-RPC arguments, results — is readable there.
Sealing encrypts it so that only the attested TD can read it, and only the
TD can produce a response the client accepts. It is a port of mero-js
src/sealed/sealed.ts and noise.ts, byte for byte with core’s
crates/server/src/sealed.rs (its published vectors are the tests).
The wire
Section titled “The wire”handshake Noise_NK_25519_AESGCM_SHA256, prologue "calimero/sealed-http/v2" request POST /sealed/v2/handshake 0x02 ‖ transport_pk ‖ Noise message 1 response 0x02 ‖ Noise message 2 (payload = session_id(16) ‖ lifetime secs, u32 BE)exchange POST /sealed/v2 header 0x02 ‖ session_id(16) ‖ request_id (u64 BE; from 1, each used once) request header ‖ AES-256-GCM(req key, nonce = request_id ‖ 0u32, aad = header, inner) response frames, each u32 BE length ‖ AES-256-GCM(resp key, nonce = request_id ‖ index, aad = header, kind ‖ data) kind 0 = head (JSON), 1 = body bytes, 2 = end; no end frame = cut shortSealedTransport.handshakePath, .sealedPath and .contentType
(application/vnd.calimero.sealed) name the routes. The transport key only
authenticates the node; the session keys come from both sides’ ephemeral
keys, so once a session is gone (an hour at most) what was sent in it cannot
be opened, not even with the transport key.
A sealed transport
Section titled “A sealed transport”// A node known only by URL: attest its transport key, then seal everything to it.let sealed = try SealedTransport.attested(baseURL: nodeURL, verifier: try createSignedReleaseVerifier( SignedReleaseVerifierOptions(release: { try await fetchNodeRelease(url) }, profile: "locked-read-only", minReleaseVersion: "2.3.99")))
let (data, response) = try await sealed.data(for: request) // or send(_:) to stream the bodylet mero = Mero(config: MeroConfig(baseURL: nodeURL), session: sealed.urlSession())SealedTransport(baseURL:key:session:)takes the key as.key(Data)or.attest { ... }(typicallyfetchAttestedTransportKey(_:verifier:)). Given a function, a restarted node is attested again through it and the request retried once; given bytes, a restart throwsStaleTransportKeyError.urlSession(configuration:)hands anyURLSessionconsumer —Mero,RelayClient,URLSessionHttpClient, the SSE clients — a session whose every request is sealed (SealedURLProtocol). Server-sent events stream sealed, frame by frame. WebSockets cannot be sealed.- A request for a URL outside the base URL is refused, never sent in the clear.
- One session serves many requests, concurrent ones included, renewed a minute
before the node would drop it; a forgotten session (
unknown_session) is reopened and the request resent; abusyhandshake waitsRetry-After(≤ 5 s) once. Any other envelope refusal is aSealedTransportError(status,code). createSignedReleaseSealedFetch(_:)is the whole recipe for a relay: the release it names in/admin-api/tee/info(orreleaseVersionfor a node that refuses that unsealed), fromreleaseSource, verified, then attested and sealed.transportKeyBinding(_:_:)is what the quote’s report data carries:SHA-256("calimero.tee-attest.transport-key.v1" ‖ inner ‖ key).
Sealing a relay
Section titled “Sealing a relay”let connection = try await connectCloud(ConnectCloudOptions( authorAccount: account, authorProof: credential, keys: keys, sessionToken: token, seal: DcapRelayKeyVerifier.signedReleaseVerifier(relayURL: relayURL)))With seal, every call to the relay — the warrant, the method’s arguments,
the login, its reads — is sealed to the key its attested TD holds; calls to
the cloud stay plain (they carry no intent).
Delegated sessions seal a hosted relay by default
(DelegatedEnvironment.sealRelays, CloudConfig.sealRelays):
DelegatedAccounts.relayTransportSession(_:env:) is the one sealed session
per relay their clients, logins and governance share, and
DelegatedAccounts.forgetRelaySealing() drops them. A loopback relay is never
sealed.