Skip to content

net/http: give the HTTPS client trust roots on darwin - #75

Open
yohimik wants to merge 1 commit into
tinygo-org:mainfrom
yohimik:upstream-pr/http-darwin-roots
Open

net/http: give the HTTPS client trust roots on darwin#75
yohimik wants to merge 1 commit into
tinygo-org:mainfrom
yohimik:upstream-pr/http-darwin-roots

Conversation

@yohimik

@yohimik yohimik commented Aug 30, 2026

Copy link
Copy Markdown

net/http: give the HTTPS client trust roots on darwin

Repository tinygo-org/net. Branch upstream-pr/http-darwin-roots, base
main.

What this does

The client dialled TLS with a nil config, which leaves RootCAs nil, which
sends crypto/x509 to the platform verifier on darwin.
crypto/x509/internal/macos in TinyGo is a stub, so every HTTPS request to a
real server fails verification as soon as the package compiles against the
standard library crypto/tls and not against the no-op stub.

The dial now takes a config from defaultTLSConfig.

  • Off darwin that is still nil, because crypto/x509 finds the system roots in
    the usual files.
  • On darwin it carries a pool that is read once from $SSL_CERT_FILE, or from
    the bundle of macOS at /etc/ssl/cert.pem. A non-nil pool makes x509 build
    the chain in pure Go instead of a call to the stubbed verifier.
  • If no file can be read, the config has no roots, so the result is an ordinary
    verification error and not a check that is silently skipped.

Evidence

There is no CI in this repository. Checked by hand on macOS 26.6 arm64 with a
TinyGo build that carries the matching toolchain change.

  • http.Get("https://example.com/") completes with a verified chain.

A downstream product ships binaries built with these changes in a production
release. dispat v1.4.0 is published and is not a prerelease. It carries
dispat-tiny-linux-amd64 and dispat-tiny-linux-arm64, built by the fork
release v0.42.0-net.4 from sha256-pinned tarballs and smoke-executed under
binfmt before upload, beside six binaries from the gc toolchain.
https://github.com/yohimik/dispat/releases/tag/services%2Fdispat%2Fv1.4.0

The acceptance record of that repository is committed at
packages/docs/docs/internals/tinygo.md. It reports the net.2 to net.4
acceptance history, an integration suite of 694 rows that passes with 0 failures
and 1 documented skip on darwin, and a size table of 0.58x to 0.63x against the
gc equivalents with TinyGo -opt=z -no-debug against go build -trimpath -ldflags "-s -w". Those figures come from that document. They are not a
measurement of this branch.

The self-update path of that program runs over real TLS against a live host, and
its suite has negative rows as well. An unknown CA is refused, and a plaintext
server on a TLS port is refused. The shipped binaries in that release are for
linux. The darwin evidence is the acceptance record and the check above.

Dependencies

  • This only matters once the TinyGo toolchain uses the standard library
    crypto/tls on hosted targets. See the tinygo PR "loader: use the real
    crypto/tls on hosted linux and darwin". With the TLS stub the change is
    harmless and has no effect.
  • http/client.go is also touched by "net/http: follow redirects in the client
    again" in this series. The two merge without a conflict, but take the redirect
    PR first and rebase this one on it, or the reverse, to keep the diff small.

Known gaps

  • A direct tls.Dial with a nil config still fails on darwin for the same
    reason. A caller that does not use this package supplies its own RootCAs.
  • The right long-term fix is a working crypto/x509/internal/macos in TinyGo.
    This is the client-side workaround until then.

Related pull requests

This change is part of one body of work. Together the changes make programs that use the network and child processes work on hosted linux and macOS. A full CLI was tested end to end with all of them and ships binaries built this way, see dispat v1.4.0 in the evidence section.

In tinygo-org/tinygo

In this repository

A merge order that works. The remaining bug fixes are independent. tinygo-org/tinygo#5633 goes before tinygo-org/tinygo#5635. HTTPS on linux needs only tinygo-org/tinygo#5633 and tinygo-org/tinygo#5635. Full darwin support also needs tinygo-org/tinygo#5636, the net changes and a new src/net submodule pin.

Related owner work

The Crier listener and close audit is separate from these PR measurements. The historical Linux arm64 run passed 142 tests with local patches. It predates later Crier changes and is not release validation.

Current integration status

Darwin fcntl work is in tinygo-org/tinygo#5612. tinygo-org/tinygo#5632 is closed and remains a history reference only. The integer and pointer tests were offered on #5612. Builder socket and spawn symbols in tinygo-org/tinygo#5636 are an independent prerequisite at 6fded0c9. The direct syscall test needs no net update, so that PR does not wait for this net series. tinygo-org/tinygo#5634 is limited to process support.

#82 supplies the separate ListenConfig implementation. Close guard tests and a limited shutdown fix stay on the fork branch codex/close-audit for coordination with the #77 owner. They are not equivalent to the poller. No second shutdown PR was opened.

Published fork and downstream evidence

The net.2 fork release combines the coordinated changes at 95fba82a, with net 0f460803. It differs from accepted candidate e7d34c8c only in the version constant. Linux, macOS and Windows branch CI and tag CI passed on their first attempts.

Dispat source 909dc401, with harness 0990c6db, passed 796 test events with no failures or skips on each native Darwin ARM64 and Linux ARM64 candidate run. Crier source 7d687fc8 passed raw and stripped E2E on native Linux ARM64 and emulated Linux AMD64, each with 144 top-level tests and 156 passing events, no failures or skips. Both applications use TinyGo-built update fixtures and test trusted TLS, certificate refusal, original backup hashes and byte-identical offline rollback. Their workflows also exercise files, environment variables, concurrency and child processes. Crier includes real FFmpeg and webrender/canvas rendering.

Crier's unchanged 13-image pixel gate passed. It uses an approved two-line explicit-rounding webrender build patch for both compilers. The earlier gradient mismatch was permitted fused arithmetic, not a TinyGo compiler error. Candidate stripped sizes are 13,835,824 versus 30,277,794 Go bytes on ARM64 (54.30% smaller), and 16,446,968 versus 32,518,306 on AMD64 (49.42% smaller).

These are combined-candidate application results, not proof that this PR alone supplies the features. They supersede the earlier Crier comparison. Published-toolchain probes and application acceptance have since completed. The final Crier v1.1.1 release evidence is below. Dispat controls its own publication. WaitDelay, in-flight deadlines and full descriptor lifetime remain open. This enables tested CLI client workflows, not general Go or server compatibility.

Owner sync, 6 September

The owner of #77 adopted our repeated-Close guard as 9dc11e1, with tests adapted to its nonblocking sockets. The owner of #80 adopted the Zone correction as 7b7aacb and removed the test dependency on #82. These are PR branches, not upstream merges.

New #83 restores RoundTripper dispatch. A Git merge check against #72 reports a conflict in http/client.go; both behaviors need combined tests before integration. New #84 uses the existing integer-conversion shim in tlssock.go. Neither new PR is included in the tested candidate e7d34c8c. No duplicate PR is needed.

Published Crier v1.1.1 evidence

Crier v1.1.1 is public at source acac2f0e and uses published TinyGo 0.43.0-net.2. Its final acceptance report and SHA-256 manifest identify the exact release bytes. The public tag, asset sizes and report digest were checked. These final sizes supersede the candidate sizes above.

Linux target Standard Go bytes Stripped TinyGo bytes Reduction
ARM64 30,277,794 13,835,840 54.30%
AMD64 32,518,306 16,447,000 49.42%

The report records 144 top-level tests and 156 passing events for each raw and stripped run on native ARM64 and emulated AMD64, with no failures or skips. It covers real CLI files, environment and concurrent work, child processes, TLS, update/rollback fixtures, uploads, real FFmpeg, and webrender/canvas rendering. The unchanged 13-image gate passes on both targets. AMD64 is exact; ARM64 has 12 exact images and four event-card pixels with channel difference 1. Both compilers use the same explicit-rounding webrender build patch. Standard Go tests, 90.6% coverage, lint and docs also pass.

This is combined-fork application evidence, not isolated proof for this PR or general server support. The generic emulated AMD64 os closure assertions still fail and also fail with ordinary Go under that emulation; they are not counted as passing. Native AMD64 CI and the final published AMD64 net package pass. The report retains other platform and deadline/descriptor limits. Tiny binaries are opt-in; normal install/self-update selects standard Go assets.

Published Dispat v1.8.1 CLI evidence

Dispat v1.8.1 CLI is public. Its size and SHA-256 manifest records build source 40c58236, Go 1.26.8 and TinyGo 0.43.0-net.2. The public asset metadata and manifest digest were checked.

Linux target Standard Go bytes TinyGo bytes Reduction
ARM64 9,896,098 6,299,032 36.35%
AMD64 10,895,522 6,697,528 38.53%

These are final published CLI asset sizes, not the earlier candidate measurements. They do not replace the separately identified test evidence or remove known runtime limits. The full release workflow has now completed successfully at the recorded build source, including its Windows, macOS and Ubuntu checks. This does not change the test and platform limits stated above.

The client dialled TLS with a nil config, which leaves RootCAs nil, which sends
crypto/x509 to the platform verifier on darwin. crypto/x509/internal/macos in
TinyGo is a stub, so every HTTPS request to a real server failed verification
as soon as the package compiled against the standard library crypto/tls and not
against the no-op stub.

Give the dial a config from defaultTLSConfig. Off darwin that is still nil,
because crypto/x509 finds the system roots in the usual files. On darwin it
carries a pool that is read once from $SSL_CERT_FILE, or from the bundle of
macOS at /etc/ssl/cert.pem. A non-nil pool makes x509 build the chain in pure
Go instead of a call to the stubbed verifier. If no file can be read, the
config has no roots, so the result is an ordinary verification error and not a
check that is silently skipped.

A direct tls.Dial with a nil config still fails on darwin for the same reason.
A caller that does not use this package supplies its own RootCAs.
@yohimik

yohimik commented Sep 2, 2026

Copy link
Copy Markdown
Author

tinygo-org/net main has not moved since this branch was opened. It is still
70037cf, so the branch needs no rebase and it is current. v0.42.0 of the
toolchain pins src/net at that same commit, so the change applies to the
released toolchain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant