package cx.lehmann.gemini.gemini; import java.security.KeyStore; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.List; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import java.net.URLDecoder; import java.io.UnsupportedEncodingException; import java.util.Date; import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; import io.vertx.core.http.ClientAuth; import io.vertx.core.net.NetServerOptions; import io.vertx.core.net.NetSocket; //import io.vertx.core.net.OpenSSLEngineOptions; import io.vertx.core.net.PemKeyCertOptions; //import io.vertx.core.net.PemTrustOptions; import io.vertx.core.net.TrustOptions; import javax.xml.bind.DatatypeConverter; import java.util.ArrayList; //import sun.security.x509.X509Cert; public class MainVerticle extends AbstractVerticle { List clients=new ArrayList<>(); // X509TrustManager tm=new MyTrustManager(); @Override public void start(Promise startPromise) throws Exception { NetServerOptions options=new NetServerOptions(); vertx.exceptionHandler(ex -> {ex.printStackTrace();}); // String certPath="c:/temp/cert.pem"; String certPath="/home/lehmann/gemini-chat/cert.pem"; // TrustOptions trustOptions=new MyTrustOptions(vertx); TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore keystore=KeyStore.getInstance("JKS"); trustMgrFactory.init(keystore); TrustManager tms[] = trustMgrFactory.getTrustManagers(); TrustManager tm=tms[0]; TrustManager trustManager = new MyTrustManager(tm); options.setPemKeyCertOptions(new PemKeyCertOptions() .setCertPath(certPath) .setKeyPath(certPath)) .setSsl(true) // .setTrustOptions(trustOptions) .setTrustOptions(TrustOptions.wrap(trustManager)) // .setOpenSslEngineOptions(new OpenSSLEngineOptions()) .setClientAuth(ClientAuth.REQUEST) ; // SSLContext sc = SSLContext.getInstance("SSL"); // sc.init(null, new X509TrustManager[] { tm }, null); // HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); vertx.createNetServer(options).connectHandler(conn -> { conn.handler(event -> { System.out.println(new Date().toString()+"accepted connection:"+conn.remoteAddress()); String url=event.toString("UTF-8"); if(!url.endsWith("\r\n")) { conn.write("40 format error\r\n"); conn.close(); } else { url=url.substring(0, url.length()-2); System.out.println("url:"+url); String path=url.substring(9); if(path.indexOf('/')>=0) { path=path.substring(path.indexOf('/')); } else { path=""; } System.out.println("path:"+path); if(path.equals("")) { conn.write("30 gemini://gemini.lehmann.cx:11965/\r\n"); conn.close(); } else if(path.startsWith("/post")) { System.out.println("post"); try { List certs = conn.peerCertificates(); Certificate cert = certs.get(0); X509Certificate certX509 = (X509Certificate) cert; String clientHash = getThumbprint(certX509); System.out.println("cert"); if (!url.contains("?")) { conn.write("10 please enter your chat message\r\n"); } else { String message = url.substring(url.indexOf('?') + 1); conn.write("20 text/gemini\r\n"); conn.write("message was sent\n"); conn.write("=> /post post another message\n"); String decodedMessage; try { decodedMessage=URLDecoder.decode(message, "utf-8"); } catch(UnsupportedEncodingException ex) { decodedMessage="error"; } String quotedMessage=decodedMessage.replace("\n", "\n "); for (NetSocket socket:clients) { socket.write(clientHash+":"+quotedMessage+"\n"); } } } catch (SSLPeerUnverifiedException | CertificateEncodingException | NoSuchAlgorithmException ex) { // ex.printStackTrace(); conn.write("60 cert required\r\n"); } conn.close(); } else { System.out.println("conn2"); for (NetSocket socket:clients) { socket.write("one client connected. count is "+(clients.size()+1)+"\n"); } clients.add(conn); conn.write("20 text/gemini\r\n"); conn.write("to post messages, go to\n"); conn.write("=> post post page\n"); conn.write("preferably in a new window\n"); conn.write("currently "+clients.size()+" reading clients are connected\n"); conn.write("chat start\n"); conn.closeHandler(v -> { System.out.println("a client closed"); clients.remove(conn); for (NetSocket socket:clients) { socket.write("one client disconnected. count is "+clients.size()+"\n"); } }); } } } ); }).listen(11965, server -> { if (server.succeeded()) { startPromise.complete(); System.out.println("Gemini server started on port 11965 at "+new Date().toString()); } else { server.cause().printStackTrace(); startPromise.fail(server.cause()); } }); } private static String getThumbprint(X509Certificate cert) throws NoSuchAlgorithmException, CertificateEncodingException { MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] der = cert.getEncoded(); md.update(der); byte[] digest = md.digest(); String digestHex = DatatypeConverter.printHexBinary(digest); return digestHex.toLowerCase(); } }