Skip to content
 
 

Repository files navigation

Melitta

A Java JSSE TLS provider backed by s2n-tls over JNI, enabling ML-KEM (post-quantum) key exchange in TLSv1.3.

Melitta uses s2n-tls with the "default_pq" security policy and aws-lc as the cryptographic backend, negotiating hybrid post-quantum key exchange (X25519MLKEM768) for TLSv1.3 connections.

Status: Proof of concept. See Current Limitations.

Prerequisites

  • JDK 8+
  • CMake 3.16+
  • C compiler (clang or gcc)
  • Network access (tests connect to AWS KMS)

Quick Start

# Clone with submodules
git clone --recurse-submodules https://github.com/WillChilds-Klein/melitta.git
cd melitta

# Build and test
./gradlew build

Usage

import com.melitta.MelittaProvider;
import java.security.Security;
import javax.net.ssl.SSLContext;
import javax.net.ssl.HttpsURLConnection;

// Register the provider
Security.insertProviderAt(new MelittaProvider(), 1);

// Create an SSLContext
SSLContext ctx = SSLContext.getInstance("TLSv1.3", "Melitta");
ctx.init(null, null, null);

// Use with HttpsURLConnection
URL url = new URL("https://example.com");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(ctx.getSocketFactory());
int status = conn.getResponseCode();

Required JVM flags (JDK 9+ only, for socket file descriptor extraction):

--add-opens java.base/java.net=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED

Build

Gradle orchestrates three staged CMake builds:

  1. aws-lc (static library) — cryptographic backend with ML-KEM support
  2. s2n-tls (shared library) — TLS implementation using "default_pq" security policy
  3. libmelitta_native (shared JNI library) — bridges s2n-tls to Java
./gradlew build     # full build + tests (~1 min clean, ~20s incremental)
./gradlew test      # run tests only
./gradlew clean     # remove all build artifacts

Tests

Test Description
KmsPingTest Raw s2n-tls handshake + HTTPS GET to KMS /ping via HttpsURLConnection
KmsListKeysV2Test AWS SDK for Java v2 KMS ListKeys via default HTTP client
KmsListKeysV1Test AWS SDK for Java v1 KMS ListKeys via Apache HttpClient

All tests connect to kms.us-east-1.amazonaws.com:443 and assert ML-KEM key exchange. The SDK tests skip gracefully when AWS credentials are unavailable.

./gradlew test --tests "com.melitta.KmsPingTest.httpsGetKmsPing"  # single test

Architecture

MelittaProvider (java.security.Provider)
  └── MelittaSSLContextSpi (SSLContextSpi)
        └── MelittaSSLSocketFactory (SSLSocketFactory)
              └── MelittaSSLSocket (SSLSocket)
                    └── S2nBinding (JNI) ↔ s2n_binding.c ↔ s2n-tls ↔ aws-lc

Key exchange

With the "default_pq" s2n-tls security policy, Melitta negotiates hybrid post-quantum key exchange. A successful handshake with a compatible server (e.g., AWS KMS) prefers X25519MLKEM768 — combining classical X25519 ECDH with ML-KEM-768 (FIPS 203).

Benchmarks

A JMH benchmark suite compares Melitta against three other client-side JSSE providers on three scenarios: handshake-only, 1 KB transfer, and 1 MB transfer. Benchmarks run against an in-process TLS 1.3 loopback server with a self-signed CA, so results reflect provider overhead rather than network variance.

Providers under test:

Provider Backend
melitta s2n-tls + aws-lc (this project)
jsse Platform default (SunJSSE)
conscrypt Conscrypt (BoringSSL)
bouncycastle BouncyCastle BCJSSE

Latest report: http://will.childs-kle.in/melitta/ (published from the gh-pages branch on every push to main, running on a shared ubuntu-latest GitHub runner).

The published numbers are meaningful for relative comparisons between providers in a single report. Absolute ms/op values depend on the runner's hardware and noisy-neighbor CPU contention and can shift by 5-10× between a laptop and a shared CI VM — do not compare across environments.

Run locally:

./gradlew :benchmarks:jmh           # full run with default iteration counts
./gradlew :benchmarks:jmhReport     # render HTML from build/results/jmh/results.json

Override iteration counts via -Pjmh.iterations=N, -Pjmh.fork=N, -Pjmh.include=<regex>, etc.

Apple Silicon note: Conscrypt does not publish a osx-aarch_64 native build. On Apple Silicon, use -Pjmh.providers=melitta,jsse,bouncycastle to skip it.

Artifact Size

The JAR bundles the JNI shared library as a resource under native/<os>-<arch>/. libs2n and libcrypto (from aws-lc) are statically linked into libmelitta_native, so the shared library has no runtime dependency on either — a single file is loaded at startup.

At init, NativeLoader extracts the platform-appropriate shared library from the JAR into a temp directory and calls System.load. The extracted file is the same size as the copy inside the JAR (shown uncompressed below).

JARs are built per-platform. Sizes as of commit 48520e0:

OS Arch JAR (packed) Native lib (unpacked)
macOS aarch64 1.5 MB 3.6 MB
macOS x86_64 ?? ??
Linux aarch64 ?? ??
Linux x86_64 1.8 MB 5.0 MB

Sizes are approximate and depend on compiler, toolchain, and aws-lc/s2n-tls commit. Only symbols actually referenced by libmelitta_native are pulled in from the static archives; the figures above reflect that dead-stripped footprint, not the full aws-lc + s2n-tls libraries.

Current Limitations

This is a proof of concept with the following constraints:

  • Client-only — no server sockets or SSLEngine support
  • No client certificate authentication
  • No session resumption or 0-RTT
  • No Java TrustManager/KeyManager integration — uses system trust store via s2n-tls
  • Requires --add-opens JVM flags for socket fd extraction via reflection

License

Apache License 2.0

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages