← Download

SuperJ v1.3

SuperJ Community Edition. Free for personal use, open source projects, non-profits, educational institutions, and small businesses (< $5M annual revenue).

Install

curl -fsSL https://superj.dev/releases/v1.3/install.sh | sh -s -- https://superj.dev/releases/v1.3

This installs SuperJ to ~/superj and adds it to your PATH. Available on macOS arm64, Linux x86_64, and Linux arm64.

BREAKING: NioAdapter.connectTls now requires a verification policy (#3365)

**If you use TLS as a client, this release will not compile-and-run your code unchanged, and that is

deliberate.** connectTls(host, port, provider, null) now throws.

The previous release's notes said the TLS client did not authenticate the server. It now does — and the

fix is not only new checks, it is removing the way you could get an unauthenticated connection without

asking for one. tlsConfig = null meant "anonymous client" — no client certificate — and silently

also meant "do not verify the server". One spelling for two unrelated statements, so callers got the

second without choosing it.

Pick one:

// Pin the server's public key (64 bytes X||Y of its P-256 point).
Pem leaf = Pem.parse(certPem, 0, certPem.length);
byte[] pin = new byte[64];
X509.spkiEcPoint(leaf.der, 0, leaf.der.length, pin, 0);
adapter.connectTls(host, port, provider, new TlsConfig().pinnedPeerKey(pin));

// Or anchor on a CA you supply, and require the hostname the certificate covers.
byte[] caDer = Pem.parse(caPem, 0, caPem.length).der;
adapter.connectTls(host, port, provider,
    new TlsConfig().trustAnchor(caDer).expectHost("api.internal"));

// Or keep the old behaviour — encrypted, accepts any man-in-the-middle — explicitly.
adapter.connectTls(host, port, provider, new TlsConfig().insecureSkipVerify(true));

Pin an endpoint you control; anchor a service that rotates its leaf, since a pin breaks on rotation.

insecureSkipVerify(true) is a legitimate choice for a loopback test or a network where interception is

already in your threat model — it is a named method precisely so that choice is visible in your code.

What is verified. Pin equality or a verifiable path to your CA; issuer/subject compared as raw DER;

an ecdsa-with-SHA256 signature over each certificate's tbsCertificate; cA=TRUE on the anchor and

every intermediate (absent counts as FALSE, RFC 5280 §4.2.1.9); the validity window of every

certificate on the path; the CertificateVerify signature over Transcript-Hash(CH..Certificate); and

subjectAltName dNSNames when expectHost is set, with wildcards matching exactly one leftmost label and

no fallback to the Common Name (RFC 2818).

Failures throw sj.crypto.TlsPeerUnverifiedException and print the reason to stderr even with no logger

configured — a silent close is indistinguishable from a network drop, and invisible rejections teach

people that verification is flaky.

Still not a public-web client, though much closer. RSA verification (PKCS#1 v1.5 and PSS), a

multi-anchor trust store, wider signature_algorithms, and a small bundle of real public roots all

landed this cycle — the pieces that were previously missing. It still does not work end to end against an

arbitrary HTTPS host: measured on this build, example.com:443 and superj.dev:443 both fail to

complete, and not at the verification step. The ClientHello sends no SNI (#3387), so a

virtual-hosted server cannot select a certificate in the first place. Until that ships, this

authenticates your own services, where you name the anchor.

openTls (the server role) is unaffected.

What's new

  • TLS client: RSA and a trust store (#3368, #3375–#3377, #3381–#3383, #3367). RSA bignum +

modular exponentiation, RSASSA-PKCS1-v1_5-VERIFY (SHA-256/384/512), RSASSA-PSS-VERIFY,

SPKI RSA key parsing, RSA-signed chain links, a wider ClientHello signature_algorithms, and

TlsConfig.trustAnchors(byte[][]) for a multi-anchor store. Pem.certBundle(pem) splits a PEM

bundle into DER anchors; a small set of real public roots ships at sdk/net/ca_bundle.pem.

See the SNI caveat above before pointing this at a public host.

  • Pem.certDer(pem) turns the PEM you have into the DER trustAnchor takes, in one call

(#3366). SessionHandler.peerCertificate() hands the application the peer's leaf before

onConnected(), for policy the engine does not do — a fingerprint pin, an OID check, an audit

log. It is not where verification happens.

  • connect() to a dead port now reports failure (#3369). It fired onConnected() and then went

silent, so a client could not tell "connecting" from "never will". SO_ERROR is probed on

connecting sockets and the failure routes to onDisconnected(). This is the regression #2695 was

filed for.

  • Two network-suite expected files no longer pin an ephemeral port and a nanoTime, so they can pass

(#3370). They had been permanently red, which is what hid #3369.

  • HTTP client fixes (#3339/#3359). WebClient.sj and hand-written clients could connect and then

receive nothing: a non-blocking connect() returns before the handshake completes, so the first

request write blocks and must be retried on the writable event.

HttpResponseListener.onError(int) is now documented — the code is always

HttpResponseReader.ERROR (2), covering a parse failure, an over-large response, or a disconnect

without a response — along with the fact that a deterministic onError(2) on the first poll means the

request was never sent.

  • strict arena reaches the same verdict on every edition (#3361). Each SDK now ships

build/escape-summaries.txt, so --sdk-path answers as --sdk-source does and superj check agrees

with build. Also fixes the loader looking one directory too high for any --sdk-path containing a

slash, which made this and the capability closure below inert for every installed user.

  • Capabilities reached through SDK wrappers are attributed (#3362), derived from the SDK's own call

graph rather than a hand-maintained list of types. Previously such a grant could be reported as

"never used — consider removing".

  • superj memlog reports arena reclaim per block (#3364) — the question W_ARENA_NO_SAVINGS cannot

answer, since it warns only when every allocation in a block escapes. Note that scoping is lexical:

an object whose constructor allocates its own payload leaves only the headers in the block. Measured

on the same 819 KB, 983040 bytes reclaimed when allocated directly versus 65536 via a constructor.

  • Process.run(String[], String, int) no longer hangs (#3363). The pipe-drain loop called poll()

with an uninitialized .events field, so it watched for whatever the caller's stack frame happened to

contain; when that did not include POLLIN it spun forever.

  • New client-side test fixtures under tests/tls_certs/: a certificate with a real subjectAltName, and a

CA plus CA-signed leaf that openssl verify accepts.

  • Diagnostics: E_ARENA_ESCAPE and W_ARENA_NO_SAVINGS no longer carry the retracted claim that the two

SDK build modes can disagree, and W_ARENA_NO_SAVINGS records that its threshold is a count of

allocation sites rather than bytes (#3360/#3364).

Earlier in v1.3

  • ACME crypto primitives (#2696): P-256 keygen/sign/verify, PEM parse/encode, JWS/JOSE (ES256), PKCS#10 CSR builder, X.509 notAfter extraction. Enables ACME (RFC 8555) certificate automation.
  • ALPN support for HTTP/2 over TLS (#2697): the TLS 1.3 engine now negotiates ALPN (RFC 7301) so the client offers h2/http/1.1 and the server selects one. The negotiated protocol is exposed on the session after onConnected(), letting the application dispatch between HTTP/1.1 and HTTP/2.
  • Memory-event log (#2681/#2682/#2683/#2688): superj memlog subcommand for debugging memory issues; --mem-track compile flag arms the arena event log; per-type histogram under SJ_MEM_DEBUG.
  • Enterprise install fix (#2692): enterprise tarball now ships both libsuperj_sdk.a and sdk.ll so superj test works without --enterprise.
  • Compiler fix: emitItable now declares default-method symbols for external (SDK) interfaces, so user code implementing an SDK interface with default methods links cleanly against the archive.
  • byte[] array literal initializer fix (#2710): field initializers like byte[] tls13Prefix = { 't','l','s','1','3',' ' } were miscompiled into 16/32-bit slots, corrupting the TLS 1.3 HKDF label prefix and breaking OpenSSL interop. Fixed by routing field initializers through checkFieldInit and narrowing arrayInitType to the declared element type.
  • Full TLS certificate chain (#2724): the TLS 1.3 server now sends the full certificate chain (leaf + intermediates) in the Certificate message, not just the leaf. Previously pemCertToDer parsed only the first PEM block; non-browser clients without AIA (openssl s_client, minimal-CA curl, Java SSL) failed to verify. setCertificateChain now iterates all PEM blocks and sendCertificate emits the complete certificate_list.
  • TLS test coverage (#2711): HKDF-Expand-Label KAT (RFC 8446 §7.1), label-prefix assertions, in-process full-handshake test, @VisibleForTesting cross-package access under --test, and tests/tls_interop.sh now runs on Linux (auto-detects OS/arch; previously macOS-only).
  • HTTP/2 partial-frame fix (#2716): preserve a partial HTTP/2 frame across reads when the buffer position is 0, preventing a parse stall on fragmented input.
  • ByteArray.putAsciiNonClamp (#2720): explicit strict variant of putAscii that rejects out-of-range ASCII instead of clamping — for code paths where silent clamping would mask a bug.
  • @VisibleForTesting (#2718): first-class annotation for cross-package private access under --test, so tests can reach package-private fields without adding public accessors.
  • End-to-end test harness (#2722): server lifecycle + blocking HTTP client for integration tests.
  • Runtime fix: runtime/proc.c replaced obsolescent usleep with nanosleep (#2733) — the old call broke Linux builds under clang-22's strict C99 (usleep requires _DEFAULT_SOURCE on glibc, not _POSIX_C_SOURCE).
  • Labeled statements (#2826/#2831/#2832/#2833): full SPEC §6.3 support — any statement can be labeled, label chains (a: b: for …), break L / continue L, labels on if/do/try/foreach, finally-unwind on labeled break/continue. 34 ported conformance tests (JLS/OpenJDK/ECJ style) + 7 finally-interaction cases.
  • IntelliJ plugin fix: single @Test run (#2838): clicking the Run glyph next to a single @Test method now runs just that test (was: ran every test in the file). The synthesized test-runner main now marshals argv so --filter <methodname> reaches the TestRunner.
  • IntelliJ plugin fix: Debug session termination (#2839): the Debug session now terminates cleanly when a program or test suite runs to completion (was: hung forever). getSession().stop() now runs on the EDT; process kill is skipped when the process already exited. End-to-end lldb test added to make test-debug.