package dev.coph.nextusweb.server.tls; import io.netty.buffer.ByteBufAllocator; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslHandler; import javax.net.ssl.SSLException; import java.io.File; import java.io.InputStream; import java.util.Objects; /** * Configuration that enables TLS (HTTPS / WSS) on the server. Holds a ready-built Netty * {@link SslContext} and produces the per-connection {@link SslHandler} the pipeline installs as * its first handler. * *
Enabling TLS is meant to be a one-liner — point the factory at a PEM certificate chain and * private key and pass the result to {@code HttpServer.withTls(...)}:
*{@code
* HttpServer.builder(443, router)
* .withTls(TlsConfig.fromPem(new File("fullchain.pem"), new File("privkey.pem")))
* .start();
* }
*
* For full control (custom cipher suites, client-auth / mutual TLS, a non-default provider) * build a Netty {@link SslContext} yourself and wrap it with {@link #fromSslContext(SslContext)}. * The {@code SslContext} is built once and reused for every connection, so it is cheap per * request.
*/ public final class TlsConfig { /** * The pre-built, shareable server SSL context. */ private final SslContext sslContext; private TlsConfig(SslContext sslContext) { this.sslContext = Objects.requireNonNull(sslContext, "sslContext"); } /** * Builds a TLS configuration from a PEM-encoded certificate chain and an unencrypted * PKCS#8 private key. * * @param certificateChain the PEM file containing the certificate (chain) * @param privateKey the PEM file containing the PKCS#8 private key * @return a TLS configuration * @throws IllegalStateException if the certificate/key cannot be loaded */ public static TlsConfig fromPem(File certificateChain, File privateKey) { return fromPem(certificateChain, privateKey, null); } /** * Builds a TLS configuration from a PEM-encoded certificate chain and a (optionally * password-protected) PKCS#8 private key. * * @param certificateChain the PEM file containing the certificate (chain) * @param privateKey the PEM file containing the PKCS#8 private key * @param keyPassword the password protecting {@code privateKey}, or {@code null} if none * @return a TLS configuration * @throws IllegalStateException if the certificate/key cannot be loaded */ public static TlsConfig fromPem(File certificateChain, File privateKey, String keyPassword) { Objects.requireNonNull(certificateChain, "certificateChain"); Objects.requireNonNull(privateKey, "privateKey"); try { return new TlsConfig(SslContextBuilder.forServer(certificateChain, privateKey, keyPassword).build()); } catch (SSLException | RuntimeException e) { throw new IllegalStateException("Failed to initialise TLS from PEM files", e); } } /** * Builds a TLS configuration from PEM-encoded streams, for certificates/keys loaded from the * classpath or another non-file source. The caller retains ownership of the streams. * * @param certificateChain a stream of the PEM certificate (chain) * @param privateKey a stream of the PEM PKCS#8 private key * @param keyPassword the password protecting {@code privateKey}, or {@code null} if none * @return a TLS configuration * @throws IllegalStateException if the certificate/key cannot be loaded */ public static TlsConfig fromPem(InputStream certificateChain, InputStream privateKey, String keyPassword) { Objects.requireNonNull(certificateChain, "certificateChain"); Objects.requireNonNull(privateKey, "privateKey"); try { return new TlsConfig(SslContextBuilder.forServer(certificateChain, privateKey, keyPassword).build()); } catch (SSLException | RuntimeException e) { throw new IllegalStateException("Failed to initialise TLS from PEM streams", e); } } /** * Wraps a fully configured Netty {@link SslContext}, for advanced setups such as custom cipher * suites or mutual TLS. * * @param sslContext a server-mode SSL context * @return a TLS configuration backed by the given context */ public static TlsConfig fromSslContext(SslContext sslContext) { return new TlsConfig(sslContext); } /** * Creates a new per-connection {@link SslHandler} from the shared context. * * @param alloc the channel's buffer allocator * @return a fresh TLS handler for one connection */ public SslHandler newHandler(ByteBufAllocator alloc) { return sslContext.newHandler(alloc); } /** * Returns the underlying Netty SSL context. * * @return the shared server SSL context */ public SslContext sslContext() { return sslContext; } }