Mac mini — VNC client tunnel to kelp

TLS hub · Mac mini · Kelp VNC endpoints

Installed 2026-10-31 on root@192.168.0.179. Hardware reports Macmini3,1; hostname is katherine-critchleys-macbook.local; OS X 10.11.6. The previous documented LAN address was 192.168.0.15.

Using the tunnel

In Chicken of the VNC, use host 127.0.0.1, display 1, with no VNC password. For viewers that take a TCP port, use 5901. The local listener is loopback only. The VNC endpoint offers SecurityTypes None; mutual TLS authenticates the Mac before VNC is reachable.

Route: Mac 127.0.0.1:5901 → stunnel client → kelp.critchley.biz:15901 (mutual TLS) → kelp 127.0.0.1:5901 → kelp's Xtigervnc desktop. Kelp port 15900 leads to pomelo instead; 15905 leads to Radagast. Those paths were not changed.

Certificate and software

The client private key was generated on the Mac and never copied off it. Only the CSR travelled to pomelo, where it was signed with /home/john/ssl/C/CA/cert and CA/key, matching the CA deployed by /home/john/ansible/setup_server.yml. No CA private key or existing client's private key was copied to the Mac. The public CA fingerprint was compared with kelp's live /etc/stunnel/server.pem.

Client certificate: CN=katherine-critchleys-macbook; serial DAEBF7351DDFC23AC85DABB944FDCF5D; valid 2026-10-31 to 2035-01-17; RSA 4096, SHA-256, CA:FALSE and clientAuth. The private key is root:wheel mode 0600 at /usr/local/etc/stunnel/mac-client.key. Certificate mac-client.pem, public CA CA.pem, CSR mac-client.csr and configuration stunnel.conf are in the same directory.

Shared CA SHA-256 fingerprint: 13:BE:92:0E:8C:43:43:36:0F:B3:8A:B8:B4:D0:DE:02:BB:26:F5:01:62:E6:35:1C:8C:DD:5E:01:38:4E:64:65.

Source-built stunnel 5.82 is installed at /usr/local/opt/stunnel-5.82/bin/stunnel and statically linked to OpenSSL 3.5.9 from /usr/local/opt/openssl-3.5.9. The system OpenSSL and OpenSSH were left in place. Source archives, published SHA-256 files, build script and stunnel-build.log are in /var/root/build/. OpenSSL was configured for darwin64-x86_64-cc with no-shared no-module no-asm no-tests and MACOSX_DEPLOYMENT_TARGET=10.11; stunnel used --with-ssl=/usr/local/opt/openssl-3.5.9 --disable-libwrap --disable-systemd.

Both archives were verified against the upstream SHA-256 files before extraction. GitHub downloads needed a current public CA bundle from pomelo (/etc/ssl/certs/ca-certificates.crt), stored on the Mac at /var/root/build/download-ca-bundle.pem and passed with curl --cacert. stunnel.org worked with the Mac's default trust store. Certificate verification was kept enabled.

Configuration and lifecycle

foreground = yes
pid = /var/run/stunnel-kelp-vnc.pid
debug = notice
output = /var/log/stunnel-kelp-vnc.log
client = yes
verify = 2
CAfile = /usr/local/etc/stunnel/CA.pem
cert = /usr/local/etc/stunnel/mac-client.pem
key = /usr/local/etc/stunnel/mac-client.key
sslVersionMin = TLSv1.2

[kelp-desktop]
accept = 127.0.0.1:5901
connect = kelp.critchley.biz:15901
delay = yes
checkHost = John Critchley

LaunchDaemon: /Library/LaunchDaemons/biz.critchley.stunnel-kelp-vnc.plist (root:wheel, 0644), RunAtLoad and KeepAlive enabled, ten-second restart throttle. It runs the foreground stunnel binary with /usr/local/etc/stunnel/stunnel.conf and starts at boot. Logs: /var/log/stunnel-kelp-vnc.log and /var/log/stunnel-kelp-vnc-launchd.log. delay=yes resolves kelp's DNS when connecting rather than pinning its startup address.

# Inspect the service and local listener (on the Mac, as root).
launchctl list biz.critchley.stunnel-kelp-vnc
lsof -nP -iTCP:5901 -sTCP:LISTEN
tail -30 /var/log/stunnel-kelp-vnc.log

# Stop; preserve files and certificates.
launchctl unload /Library/LaunchDaemons/biz.critchley.stunnel-kelp-vnc.plist
# Start again.
launchctl load /Library/LaunchDaemons/biz.critchley.stunnel-kelp-vnc.plist

Verification

Verified on 2026-10-31: launchd loaded the job; lsof showed stunnel listening only at 127.0.0.1:5901; a complete RFB 3.8 handshake through that local port returned root@kelp, 1920×1080. The probe used a shared session and sent no keyboard, pointer or framebuffer-update requests.

A separate authenticated openssl s_client probe negotiated TLS 1.3 / TLS_AES_256_GCM_SHA384, verified the CA chain and the peer name John Critchley, and received the VNC banner. A probe without the client certificate was rejected: TLS 1.2 returned handshake failure (alert 40); kelp's stunnel-vnc journal also confirmed the TLS 1.3 attempt failed with peer did not return a certificate and forwarded zero bytes. TLS 1.3 s_client with stdin immediately at EOF can exit zero before observing the server's client-certificate rejection; its exit code alone is not a valid negative test.

Application probe: /usr/bin/python /var/root/build/check-vnc.py. Upstream OpenSSL's test suite was disabled for this build; verification covered certificate purpose/chain, hostname, real TLS negotiation, loopback binding and the VNC application protocol. The interactive Chicken viewer was not driven during setup.

version 1  ·  created 2026-10-31  ·  updated 2026-10-31  ·  tags ssl, stunnel, vnc, mac, kelp