Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 70 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ Capability-gated networking plugins for the [Wago](https://github.com/wago-org/w
WebAssembly runtime, backed initially by [lneto](https://github.com/soypat/lneto).
UDP, TCP, DNS, bounded ICMPv4 echo, explicit-clock NTP, bounded IPv4 multicast
DNS, DHCPv4, IPv4 link-local/APIPA, configured IPv6 TCP transport enablement,
bounded ICMPv6/NDP, the pinned bounded initial DHCPv6 acquisition subset, and a
granular outbound client-only TLS capability are implemented today.
bounded ICMPv6/NDP, the pinned bounded initial DHCPv6 acquisition subset, and
granular standard-Go TLS client/server stream foundations are implemented today.
HTTP/HTTPS APIs and portable TinyGo TLS are not implemented.

> [!WARNING]
> This module is private and experimental. Use it only with the exact Wago
Expand Down Expand Up @@ -112,29 +113,71 @@ if err := wagonettls.Register(network, wagonettls.WithClientProfile(profile)); e
}
```

Inbound TLS is also explicit and does not imply raw TCP:

```go
serverProfile, err := wagonettls.NewServerProfile(2, hostServerTLSConfig,
wagonettls.RequireServerALPN("h2"),
)
if err != nil {
return err
}
if err := wagonettls.Register(network,
wagonettls.WithServerProfile(serverProfile),
wagonettls.AllowListeners(),
); err != nil {
return err
}
```

Storing a server profile alone grants no listen authority. Client and server
certificate chains are parsed eagerly, leaf keys must match standard in-memory
RSA, NIST ECDSA, or Ed25519 private keys, and credentials never enter guest
memory. Arbitrary `crypto.Signer`, HSM, clock, and dynamic certificate/config
callbacks are rejected so host code cannot indefinitely block TLS worker
teardown. Static SNI selection is limited to host-supplied immutable
certificates. Rotation drains accepted streams before replacing the listener
with a new immutable profile; closing the pinned lneto listener is an abort
boundary for streams that have not drained, so zero-downtime same-port handoff is
not claimed. `tls.ValidationTime` supplies an optional frozen validation instant
without retaining a caller callback; otherwise Go's system clock is used.

TLS intentionally has no `tls/register` zero-configuration extension and no
`net-tls` custom-CLI key. Trust roots, verification identities, ALPN, client
credentials, and profile IDs are deployment authority that must be supplied by
explicit Go composition; the repository does not invent placeholder TLS policy.

The complete TLS implementation is standard-Go-only. TinyGo 0.41.1 lacks the
required `crypto/tls` client APIs, so the repository provides no TinyGo stub,
placeholder guest module, or fake handshake. `scripts/tinygo-supported-test.sh`
tests the exact reviewed non-TLS package surface and fails closed if the five
standard-Go-only TLS packages change without review. `scripts/tls-signoff.sh`
retains separate ordinary and race evidence for explicit TLS composition,
security, ABI, mixed transport, EOF, quota, and worker teardown. TLS remains
client-only, granular-only, outside aggregate `register`, and experimental until
the complete strict release and executed arm64 requirements are satisfied.

Profiles are finite and host-defined. The guest selects only a profile ID,
remote IP endpoint, and authorized verification name. Certificate-chain and
DNS/IP SAN verification are mandatory; Common Name fallback, key logging,
renegotiation, arbitrary verification/certificate callbacks, guest session
caches, 0-RTT, STARTTLS, and wrapping guest TCP handles are absent. TLS 1.3 is
the default and TLS 1.2 requires `EnableTLS12()`. Client private keys remain
host-side. Clean `close_notify` maps to EOF; raw TCP EOF maps to TLS protocol
failure. See [`docs/tls.md`](docs/tls.md).
`net-tls` custom-CLI key. Trust roots, verification identities, ALPN, client or
server credentials, listen authority, and profile IDs are deployment authority
that must be supplied by explicit Go composition; the repository does not invent
placeholder TLS policy.

The cryptographic TLS implementation is standard-Go-only. TinyGo 0.41.1 lacks
the required arbitrary-stream `crypto/tls` APIs, so the repository provides no
TinyGo stub, placeholder guest module, or fake handshake.
`scripts/tinygo-supported-test.sh` tests the exact reviewed non-TLS package
surface and fails closed if the five standard-Go-only TLS packages change
without review. `scripts/tls-signoff.sh` retains separate ordinary and race
evidence for explicit TLS composition, client/server handshakes, ABI
compatibility, mixed transport, EOF, quota, and worker teardown. TLS remains
granular-only and outside aggregate `register`; HTTP, HTTPS, portable TinyGo TLS,
strict release adoption, and executed arm64 evidence remain incomplete.

Profiles are finite and host-defined. Outbound guests select only a profile ID,
remote IP endpoint, and authorized verification name; inbound guests select a
server profile ID and an explicitly authorized local endpoint. Certificate-chain
and DNS/IP SAN verification are mandatory for clients; configured mTLS uses
standard client-chain verification. Hosts may explicitly enable a finite,
per-instance client resumption cache with `EnableClientSessionResumption` and
ordered stateless server ticket keys with `EnableServerSessionTickets`; cache
entries and serialized bytes are bounded, quota-reserved, cleared at teardown,
and never enable 0-RTT. Common Name fallback, key logging, renegotiation,
arbitrary verification/certificate/clock/signer callbacks, guest-supplied
session caches, 0-RTT, STARTTLS, and wrapping guest TCP handles are absent. TLS
1.3 is the default and TLS 1.2 requires `EnableTLS12()`. Client private keys
remain host-side and use the same bounded software-key restriction. Clean
`close_notify` maps to EOF; raw TCP EOF maps to TLS protocol
failure. The additive `connection_info_v2` reports client/server role and peer
authentication while preserving `connection_info_v1` byte-for-byte; the fixed
`channel_binding` import returns the 32-byte RFC 9266 `tls-exporter` binding only
after authenticated completion. See
[`docs/tls.md`](docs/tls.md).

TCP defaults provide eight finite outbound streams and no listeners. UDP defaults
provide eight finite sockets, ephemeral wildcard client binds, outbound ordinary
Expand Down Expand Up @@ -206,8 +249,9 @@ operation bitset and work operations return `NOT_SUPPORTED` without output
mutation. Registering only DHCPv6 exposes `net.info`, `net.dhcpv6`, the shared
ABI import, and seven `wago_net_dhcpv6` imports; it becomes operational only
with a separately configured scoped link-local IPv6 identity. Registering only TLS
exposes exactly `net.info` and `net.tls`, `wago_net.abi_version`, and nine
`wago_net_tls` imports; it does not expose `net.tcp` or `wago_net_tcp`.
exposes exactly `net.info` and `net.tls`, `wago_net.abi_version`, and thirteen
`wago_net_tls` imports on the server-foundation branch; it does not expose
`net.tcp` or `wago_net_tcp`.
This exact TLS surface is inspected through explicit composition fixtures rather
than a self-registering extension. Unregistered protocol imports are absent and fail normal WebAssembly import resolution. The public TCP,
UDP, DNS, ICMPv4, NTP, mDNS, DHCPv4, link-local, IPv6, ICMPv6, DHCPv6, and TLS facades each construct
Expand Down
64 changes: 35 additions & 29 deletions abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,41 @@ package net
// These public compatibility constants intentionally remain literal values so
// the protocol-neutral root package does not import protocol ABI packages.
const (
AddressV1Size uint32 = 32
HandleV1Size uint32 = 8
UDPReceiveResultV1Size uint32 = 48
TCPStreamV1Size uint32 = 72
TCPIOResultV1Size uint32 = 8
TLSStreamV1Size uint32 = 72
TLSIOResultV1Size uint32 = 8
TLSConnectionInfoV1Size uint32 = 144
TLSMaxALPNV1Bytes uint32 = 32
DNSNameV1Size uint32 = 260
DNSQueryV1Size uint32 = 268
DNSRecordV1Size uint32 = 560
ICMPv4EchoRequestV1Size uint32 = 48
ICMPv4EchoResultV1Size uint32 = 48
ICMPv6EchoRequestV1Size uint32 = 48
ICMPv6EchoResultV1Size uint32 = 48
ICMPv6NeighborKeyV1Size uint32 = 32
ICMPv6NeighborV1Size uint32 = 40
ICMPv6OperationsV1Size uint32 = 4
DHCPv6OperationsV1Size uint32 = 4
DHCPv6ConfigurationV1Size uint32 = 3368
NTPSampleV1Size uint32 = 72
MDNSNameV1Size uint32 = 260
MDNSQueryV1Size uint32 = 268
MDNSRecordV1Size uint32 = 832
MDNSAnnouncementV1Size uint32 = 8
PollBudgetV1Size uint32 = 24
PollEventV1Size uint32 = 16
PollResultV1Size uint32 = 24
AddressV1Size uint32 = 32
HandleV1Size uint32 = 8
UDPReceiveResultV1Size uint32 = 48
TCPStreamV1Size uint32 = 72
TCPIOResultV1Size uint32 = 8
TLSStreamV1Size uint32 = 72
TLSIOResultV1Size uint32 = 8
TLSConnectionInfoV1Size uint32 = 144
TLSConnectionInfoV2Size uint32 = 144
TLSChannelBindingV1Size uint32 = 32
TLSMaxALPNV1Bytes uint32 = 32

TLSConnectionInfoV2FlagResumed uint32 = 1 << 0
TLSConnectionInfoV2FlagServerRole uint32 = 1 << 1
TLSConnectionInfoV2FlagPeerAuthenticated uint32 = 1 << 2
DNSNameV1Size uint32 = 260
DNSQueryV1Size uint32 = 268
DNSRecordV1Size uint32 = 560
ICMPv4EchoRequestV1Size uint32 = 48
ICMPv4EchoResultV1Size uint32 = 48
ICMPv6EchoRequestV1Size uint32 = 48
ICMPv6EchoResultV1Size uint32 = 48
ICMPv6NeighborKeyV1Size uint32 = 32
ICMPv6NeighborV1Size uint32 = 40
ICMPv6OperationsV1Size uint32 = 4
DHCPv6OperationsV1Size uint32 = 4
DHCPv6ConfigurationV1Size uint32 = 3368
NTPSampleV1Size uint32 = 72
MDNSNameV1Size uint32 = 260
MDNSQueryV1Size uint32 = 268
MDNSRecordV1Size uint32 = 832
MDNSAnnouncementV1Size uint32 = 8
PollBudgetV1Size uint32 = 24
PollEventV1Size uint32 = 16
PollResultV1Size uint32 = 24

UDPReceiveFlagTruncated uint32 = 1

Expand Down
126 changes: 126 additions & 0 deletions agent-todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -1610,3 +1610,129 @@ No repository-owned workstream or completion criterion from this hardening reque
topology, so the lifecycle/preview-1 integration must be re-reviewed and
re-ported. No provenance or review-bundle hashes were produced. Production
readiness also still requires executed arm64 TLS evidence.

## TLS client/server stream completion — July 20, 2026

- Preserved `connection_info_v1` byte-for-byte: offset 68 is again only the
little-endian resumed boolean 0 or 1. Added the separate additive
`connection_info_v2` import for resumed, server-role, and peer-authenticated
flags; unknown flags remain reserved and invalid.
- Changed server-profile composition so storing host credentials grants no
endpoint authority. `tls.AllowListeners()` is now the explicit ordinary
inbound TLS grant, remains separate from raw TCP, and continues to honor
applicable raw-TCP inbound deny rules.
- Server profile construction now eagerly parses every certificate, verifies
chain linkage and leaf/signer public-key equality, clones certificate
metadata, rejects dynamic SNI/configuration callbacks, and documents that the
host retains lifetime/concurrency ownership of each `crypto.Signer`.
- Added deterministic live two-namespace lneto TLS tests for ordinary server
authentication and mTLS, ALPN, role metadata, multi-queue backpressure,
bidirectional plaintext, clean two-way `close_notify`, abrupt raw-transport
truncation, listener reuse, exact quota/port release, close/accept races, and
concurrent namespace/resource teardown.
- The hosted ordinary-Go failure in Actions run `29757140541`, job
`88402308965`, was a real final-flight test deadlock: the client reported
verified completion before its final TLS 1.3 flight had been pumped to the
standard-library server. The test and client benchmark now explicitly service
that bounded flight before waiting for peer completion.
- Current local evidence: `go test ./...`, shuffled tests, full race/shuffle,
vet, source boundaries, checkptr, accepted-diagnostic linux/386, all 123
TinyGo-supported packages, all 12 custom CLI bundles, and all 17 TLS signoff
profiles passed. TLS signoff now resolves 164 named tests after bounded
resumption, channel-binding, client-certificate, frozen-clock,
software-signer, static-SNI, and certificate-rotation coverage. Fuzz smoke
passes 47
targets in 33 packages, including seven TLS-owned targets. Benchmark smoke
passes 173 top-level targets; the five-by-200 ms capture expands to 196 result
names and includes separate client/server TLS 1.3 handshakes. Four arm64 test
binaries cross-compile, while execution remains `skipped-no-runner`.
- Standard-Go TLS client/server streams and listeners are now implemented and
exercised. HTTP, HTTPS, portable TinyGo TLS, strict release adoption, and
executed arm64 evidence remain explicitly incomplete. PR #3 must remain a
draft and TLS must remain outside aggregate `register`.

## PR #3 TinyGo CI watchdog — July 25, 2026

- Actions run `29883882777`, job `88810323462`, reached GitHub's six-hour job
limit while the first supported package (`github.com/wago-org/net`) was still
running under TinyGo. The previous hosted TinyGo matrix had completed in about
45 minutes, and the same root package completed locally under TinyGo 0.41.1,
so the observed run is treated as a wedged package attempt rather than evidence
that the supported-package boundary changed.
- `scripts/tinygo-supported-test.sh` now runs every package verbosely behind a
ten-minute watchdog, prints a timed-out attempt, retries a timeout once, and
preserves only the final attempt in the canonical per-package log inventory.
Non-timeout failures are not retried. The timeout and retry bounds are
configurable for focused validation but remain finite and fail closed.
- Added a regression with a fake TinyGo process that wedges the root package on
its first attempt, proving the watchdog retries exactly once and still covers
all 123 supported packages.
- The remaining feature backlog at that point was HTTP/HTTPS APIs, portable
TinyGo TLS, executed arm64 evidence, strict release adoption, and the
separately documented protocol-expansion exclusions.

## Bounded standard-Go TLS resumption — July 25, 2026

- TinyGo TLS remains explicitly unsupported; no compatibility shim, plaintext
wrapper, or alternative cryptographic engine was added.
- Added opt-in `EnableClientSessionResumption(maxEntries, maxBytes)`. Every
backend instance constructs its own cache, so tickets never cross Wago
instance ownership. The cache stores serialized standard-library state under
exact entry/byte bounds, uses bounded LRU eviction, forces early-data state
off, clears retained ticket/state bytes on eviction and teardown, and rejects
caller-supplied arbitrary cache implementations.
- Added opt-in `EnableServerSessionTickets(keys...)` with one to four explicit,
unique, nonzero 32-byte keys. The first key issues tickets and the bounded
ordered set accepts retained rotation keys; ambient generation and mutable
guest key authority remain absent.
- Resumption cache capacity is conservatively reserved from the exact
per-instance queued-byte quota before cache allocation and released exactly
once on teardown. Aggregate configured cache capacity remains under the 64 MiB
TLS retention ceiling.
- Added standard-Go TLS 1.3 tests proving a full first handshake, resumed second
handshake through `[new, old]` key rotation, resumed third handshake with only
the new key, per-instance cache isolation, LRU/byte rejection, and exact quota
rollback/release. Existing `connection_info` and `connection_info_v2` already
expose the resumed bit without an ABI change.
- Graceful stream shutdown was already complete: `shutdown_write` drains accepted
plaintext and emits `close_notify`, while peer `close_notify` becomes stable
EOF. Resource `close` deliberately remains the deterministic abort path and
never waits for peer packets.
- Added the fixed `channel_binding` import for the 32-byte RFC 9266
`tls-exporter` binding. The label, context, and length are not guest-selectable;
it becomes available only after authenticated completion and preserves the
complete output on `AGAIN` or failure. Standard-library peers prove both sides
derive identical bytes.
- STARTTLS/existing-handle transfer, DTLS, QUIC TLS, 0-RTT, arbitrary dynamic
callbacks, and live mutation of immutable profiles remain separate authority
or transport designs rather than incomplete behavior in the bounded TLS
stream module.
- Current validation passes `go test ./...`, focused TLS race tests, `go vet
./...`, source-boundary checks, shell syntax, diff checks, and all 17 TLS
signoff package runs resolving 164 named tests.

## Bounded TLS host-call hardening — July 26, 2026

- Client and server profile construction now rejects caller-supplied
`tls.Config.Time` callbacks. Hosts may use `tls.ValidationTime` to install one
immutable UTC-normalized validation instant through package-owned code, or
omit it to use Go's system clock.
- Client certificate chains now receive the same eager DER parsing, chain-link
signature checks, and leaf/private-key correspondence validation as server
certificates before a profile can be registered.
- Client and server private keys are restricted to standard in-memory RSA,
NIST ECDSA, and Ed25519 implementations. Arbitrary `crypto.Signer` wrappers,
including HSM/delegated signers without a cancellable `Sign` contract, fail
closed before guest traffic can start a TLS worker.
- This closes the in-process teardown gap where an arbitrary signer or clock
callback could block inside `crypto/tls` while stream close waited for its
bounded worker set to exit. External signer support remains intentionally
unsupported until it can use a killable, finite host operation boundary.
- Static SNI selection is proven against multiple immutable certificates.
Certificate rotation is proven through a drain-close-relisten sequence: old
streams complete first, then a listener using the new profile presents a
different peer SPKI on the same endpoint. Closing the pinned lneto listener
before accepted streams drain remains an explicit abort boundary rather than
a zero-downtime handoff claim.
- Standard Go passes across the complete repository, and all 17 TLS signoff
package runs now resolve and pass 164 named test targets.
Loading
Loading