Add pem-api module: PEMDecoder/PEMEncoder (JEP 538 preview on JDK 27, JEP 542 final in JDK 28) for JWT keys, TLS, encrypted keys and BouncyCastle migration

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CifR2BE7PBvRy5hT8TS9oz
This commit is contained in:
2026-10-26 01:50:05 +00:00
co-authored by Claude Sonnet 5.5
parent c3cc57f482
commit 2e25042a68
34 changed files with 895 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;
import java.io.EOFException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import javax.net.ssl.*;
/** Build an SSLContext from three PEM files and talk to ourselves over TLS. */
public class TlsFromPem {
static List<Certificate> readAll(String pemText) throws Exception {
List<Certificate> out = new ArrayList<>();
try (InputStream in = new java.io.ByteArrayInputStream(pemText.getBytes())) {
PEMDecoder d = PEMDecoder.of();
while (true) {
try { out.add(d.decode(in, X509Certificate.class)); }
catch (EOFException end) { break; }
}
}
return out;
}
public static void main(String[] args) throws Exception {
PEMDecoder d = PEMDecoder.of();
Fx.h("read a two-certificate chain file");
List<Certificate> chain = readAll(Fx.read("server-chain.pem"));
for (Certificate c : chain) System.out.println(" " + ((X509Certificate) c).getSubjectX500Principal());
Fx.check(chain.size() == 2, "the stream decoder returns the two certificates one by one");
Fx.h("server side: key + chain -> KeyStore -> KeyManagerFactory -> SSLContext");
PrivateKey key = d.decode(Fx.read("server-key.pem"), PrivateKey.class);
char[] pw = new char[0];
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null, null);
ks.setKeyEntry("server", key, pw, chain.toArray(new Certificate[0]));
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, pw);
SSLContext serverCtx = SSLContext.getInstance("TLS");
serverCtx.init(kmf.getKeyManagers(), null, null);
HttpsServer server = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
server.setHttpsConfigurator(new HttpsConfigurator(serverCtx));
server.createContext("/", ex -> {
byte[] body = "hello over TLS\n".getBytes();
ex.sendResponseHeaders(200, body.length);
ex.getResponseBody().write(body);
ex.close();
});
server.start();
URI uri = URI.create("https://localhost:" + server.getAddress().getPort() + "/");
try {
Fx.h("client trusting only the CA certificate from ca.pem");
KeyStore ts = KeyStore.getInstance("PKCS12");
ts.load(null, null);
ts.setCertificateEntry("ca", d.decode(Fx.read("ca.pem"), X509Certificate.class));
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
SSLContext clientCtx = SSLContext.getInstance("TLS");
clientCtx.init(null, tmf.getTrustManagers(), null);
HttpClient trusting = HttpClient.newBuilder().sslContext(clientCtx).build();
HttpResponse<String> r = trusting.send(HttpRequest.newBuilder(uri).build(), HttpResponse.BodyHandlers.ofString());
System.out.println("HTTP " + r.statusCode() + " " + r.body().strip() + " (" + r.sslSession().get().getProtocol() + ")");
System.out.println("server presented: " + ((X509Certificate) r.sslSession().get().getPeerCertificates()[0]).getSubjectX500Principal());
Fx.check(r.statusCode() == 200, "request over TLS succeeds");
Fx.h("same request with the JVM's default trust store (the CA is not in it)");
try {
HttpClient.newHttpClient().send(HttpRequest.newBuilder(uri).build(), HttpResponse.BodyHandlers.ofString());
} catch (Exception e) {
System.out.println(e);
}
} finally {
server.stop(0);
}
}
}