Security can be tricky, HTTPS and TLS no less so. There are many configuration details to be aware of, and the encryption algorithms and cipher suites are moving targets, with new vulnerabilities and fixes all the time. This example does not go into all these details, but instead shows a basic example of how to bring up a HTTPS server using a self-signed TLS 1.2 key and certificate.

The main component of Java TLS communication is the Java Secure Socket Extension (JSSE). A number of algorithms and cryptographic providers are supported. The central class is the SSLContext, supported by the KeyStore. These classes load and initialise the relevant keys, certificates, and protocols which are later used by a HTTPS server (or client). See the Oracle blog, for another brief introduction to TLS 1.2, and tips on diagnosing the communication. In particular, notice the unlimited strength implementations, which have to be downloaded separately, and copied to JAVA_HOME/lib/security.

The example below shows how the certificate and key are loaded from a Java KeyStore (.jks) file, and used to initialise the SSLContext with the TLS protocol. The SSLContext is passed to the SUN HttpsServer through a HttpsConfigurator. The HttpsServer implementation takes care of the rest, and the static file handler is the same as seen in the plain HTTP based example.

  void start() throws Exception {
    HttpsServer httpsServer = HttpsServer.create(new InetSocketAddress(PORT), 0);

    SSLContext sslContext = getSslContext();
    httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));

    httpsServer.createContext("/secure", new StaticFileHandler(BASEDIR));
    httpsServer.start();
  }

  private SSLContext getSslContext() throws Exception {
    KeyStore ks = KeyStore.getInstance("JKS");
    ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PASSWORD.toCharArray());

    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    kmf.init(ks, KEY_PASSWORD.toCharArray());

    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
    tmf.init(ks);

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

    return sslContext;
  }

The code also includes a hard-coded generation of the key and certificate. A normal server would of course not implement this, but it’s included here to make the example self-contained and working out of the box. The following command is executed, and a Java KeyStore file containing a RSA based 2048 bits key, valid for one year. The keytool command will either prompt for name and organisational details, or these can be passed in using the dname argument. Also notice that password to the keystore and key are different. Further usage details on keytool can be found here.

keytool -genkey -keyalg RSA -alias some_alias -validity 365 -keysize 2048  \
  -dname cn=John_Doe,ou=TestOrgUnit,o=TestOrg,c=US -keystore /tmp/test.jks -storepass pass_store -keypass pass_key

Since the certificate is self-signed, a modern browser will yield a warning, and not allow the communication to continue without an explicit exception, as seen below. For the sake of this example, that is fine. If we do allow the certificate to be used, we will see that the communication is indeed encrypted, “using a strong protocol (TLS 1.2), a strong key exchange (ECDHE_RSA with P-256), and a strong cipher (AES_128_GCM)”, aka “TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256”.

On the topic of keys and algorithms, Elliptic Curve Cryptography (ECC) is relevant. Digicert gives a brief introduction, with details on how to generate keys for Apache. Openssl has further information on EC using openssl.

The two files below is all that is needed. A key and certificate is generate if not already present under /tmp/test.jks. Go to https://localhost:9999/secure/test.txt and enable the security exception to try it out. Also notice the logging in the console window of the server.

SimpleHttpsServer.java
GitHub Raw
/* Copyright rememberjava.com. Licensed under GPL 3. See http://rememberjava.com/license */
package com.rememberjava.http;

import java.io.File;
import java.io.FileInputStream;
import java.lang.ProcessBuilder.Redirect;
import java.net.InetSocketAddress;
import java.security.KeyStore;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;

/**
 * A HTTPS server using a self-signed TLS 1.2 key and certificate generated by
 * the Java keytool command.
 * 
 * Once running, connect to https://localhost:9999/secure/test.txt
 */
@SuppressWarnings("restriction")
public class SimpleHttpsServer {

  private static final File KEYSTORE_FILE = new File(System.getProperty("java.io.tmpdir"),
      "test.jks");

  private static final String KEYSTORE_PASSWORD = "pass_store";

  private static final String KEY_PASSWORD = "pass_key";

  private static final String BASEDIR = "com/rememberjava/http";

  private static final int PORT = 9999;

  public static void main(String[] args) throws Exception {
    System.setProperty("javax.net.debug", "all");

    generateCertificate();

    new SimpleHttpsServer().start();
  }

  /**
   * Generates a new self-signed certificate in /tmp/test.jks, if it does not
   * already exist.
   */
  static void generateCertificate() throws Exception {
    File keytool = new File(System.getProperty("java.home"), "bin/keytool");

    String[] genkeyCmd = new String[] {
        keytool.toString(),
        "-genkey",
        "-keyalg", "RSA",
        "-alias", "some_alias",
        "-validity", "365",
        "-keysize", "2048",
        "-dname", "cn=John_Doe,ou=TestOrgUnit,o=TestOrg,c=US",
        "-keystore", KEYSTORE_FILE.getAbsolutePath(),
        "-storepass", KEYSTORE_PASSWORD,
        "-keypass", KEY_PASSWORD};

    System.out.println(String.join(" ", genkeyCmd));

    ProcessBuilder processBuilder = new ProcessBuilder(genkeyCmd);
    processBuilder.redirectErrorStream(true);
    processBuilder.redirectOutput(Redirect.INHERIT);
    processBuilder.redirectError(Redirect.INHERIT);
    Process exec = processBuilder.start();
    exec.waitFor();

    System.out.println("Exit value: " + exec.exitValue());
  }

  void start() throws Exception {
    HttpsServer httpsServer = HttpsServer.create(new InetSocketAddress(PORT), 0);

    SSLContext sslContext = getSslContext();
    httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));

    httpsServer.createContext("/secure", new StaticFileHandler(BASEDIR));
    httpsServer.start();
  }

  private SSLContext getSslContext() throws Exception {
    KeyStore ks = KeyStore.getInstance("JKS");
    ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PASSWORD.toCharArray());

    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    kmf.init(ks, KEY_PASSWORD.toCharArray());

    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
    tmf.init(ks);

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

    return sslContext;
  }
}
StaticFileHandler.java
GitHub Raw
/* Copyright rememberjava.com. Licensed under GPL 3. See http://rememberjava.com/license */
package com.rememberjava.http;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.Files;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

@SuppressWarnings("restriction")
public class StaticFileHandler implements HttpHandler {

  private final String baseDir;

  public StaticFileHandler(String baseDir) {
    this.baseDir = baseDir;
  }

  @Override
  public void handle(HttpExchange ex) throws IOException {
    URI uri = ex.getRequestURI();
    String name = new File(uri.getPath()).getName();
    File path = new File(baseDir, name);

    Headers h = ex.getResponseHeaders();
    // Could be more clever about the content type based on the filename here.
    h.add("Content-Type", "text/html");

    OutputStream out = ex.getResponseBody();

    if (path.exists()) {
      ex.sendResponseHeaders(200, path.length());
      out.write(Files.readAllBytes(path.toPath()));
    } else {
      System.err.println("File not found: " + path.getAbsolutePath());

      ex.sendResponseHeaders(404, 0);
      out.write("404 File not found.".getBytes());
    }

    out.close();
  }

}