Created
December 27, 2014 19:25
-
-
Save swankjesse/32d08aa551d470cb137a to your computer and use it in GitHub Desktop.
Demonstrates a bug in Jetty ALPN where resumed sessions don't get protocol callbacks.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import java.io.BufferedReader; | |
| import java.io.ByteArrayInputStream; | |
| import java.io.IOException; | |
| import java.io.InputStreamReader; | |
| import java.net.ServerSocket; | |
| import java.net.Socket; | |
| import java.net.SocketException; | |
| import java.security.KeyFactory; | |
| import java.security.KeyStore; | |
| import java.security.PrivateKey; | |
| import java.security.SecureRandom; | |
| import java.security.cert.Certificate; | |
| import java.security.cert.CertificateFactory; | |
| import java.security.spec.PKCS8EncodedKeySpec; | |
| import java.util.Arrays; | |
| import java.util.Base64; | |
| import java.util.List; | |
| import javax.net.ServerSocketFactory; | |
| import javax.net.ssl.KeyManagerFactory; | |
| import javax.net.ssl.SSLContext; | |
| import javax.net.ssl.SSLSocket; | |
| import javax.net.ssl.SSLSocketFactory; | |
| import javax.net.ssl.TrustManagerFactory; | |
| import org.eclipse.jetty.alpn.ALPN; | |
| public class SessionResumeFail { | |
| /** Returns an SSL context that connects to localhost. */ | |
| private SSLContext sslContext() throws Exception { | |
| String privateKeyBase64 = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAN4n2hJGX77UpGt0gywS" | |
| + "lE5Hxl9efYv9chIvDp4+MpimCrVGbS2V+D2xeBIBVeMKEpAJIbqbgs307+7aAKJrN9S58J+bAZdDwsJ/gIZmiQ/+" | |
| + "kIlsHTIqXjFQ64F1D9bjPWNrkuQQFQG/bz4EWhEn5BgKrqBofFh1lhIbVF4OHQI/AgMBAAECgYBw4Sb5KjD0Me/p" | |
| + "eaY8+qPIsselJcn9G6aefrKazVfAIH5IZaSMCSu9jTNJUsxlNHUCIuG0g9gO9moDn1m7LWeMCR34r9LcNXdeM9M4" | |
| + "Tbw59WFPaJgR1s8iPogCq0Ej04HjyiItXPFpj4tBw6FJLDDKJpr/1M3qIpudkmJU5YUbYQJBAPIeKfuGwnUB14OI" | |
| + "SuTwVR65uemoXEqzBZBJrh8YvRmZMtXejKubO+vMRbGkat3OQL9bsdZP5Vmuduvro2ngVFsCQQDq5K5IZays5neV" | |
| + "OHBj4yYuoM5jT1TiB2pBhZ4sZEXhF0uVcX48yS7Fi+WavDF8l/2tAp97SBpL/ETx2MpTWN7tAkBBW2kjz7XQVgXy" | |
| + "KKlm8YVYvPP1og//ziGTWIBbPpdVQKibWO8KJ+Zd+Y9aq7J5W4LY5Qy1eG3F7rWFa9955id/AkBmUsoNJaWMCDWN" | |
| + "nnFvQfNPGqPtpRBgCkTiWBeoVOIQBPrbFf6c8jLawW968YJJOVi7dkoNBULXWQSbqgmxNySdAkAyCtczXfA6U8Xn" | |
| + "p9GWFO0fujHH3MAzeDYkGb5KlWXxse291eTXboHkaI6CYwskSig8yqKZg6195IWy2E6Imbaj"; | |
| PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec( | |
| Base64.getDecoder().decode(privateKeyBase64)); | |
| KeyFactory keyFactory = KeyFactory.getInstance("RSA"); | |
| PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); | |
| String certificateBase64 = "MIIBnjCCAQegAwIBAgIBATANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlsb2NhbGh" | |
| + "vc3QwIBcNMTQxMjI3MTkwNDA2WhgPMjExNDEyMDMxOTA0MDZaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgk" | |
| + "qhkiG9w0BAQEFAAOBjQAwgYkCgYEA3ifaEkZfvtSka3SDLBKUTkfGX159i/1yEi8Onj4ymKYKtUZtLZX4PbF4EgF" | |
| + "V4woSkAkhupuCzfTv7toAoms31Lnwn5sBl0PCwn+AhmaJD/6QiWwdMipeMVDrgXUP1uM9Y2uS5BAVAb9vPgRaESf" | |
| + "kGAquoGh8WHWWEhtUXg4dAj8CAwEAATANBgkqhkiG9w0BAQsFAAOBgQAlTNKMihtmctR08lCklZafa2ZqmC2Jeud" | |
| + "LNnBB0nWmzv5RhU5BwI4YdskKY90kZaq6uAJraE8LGi7V1d/mvG+7SFEbM3mEzu+JE5Q4BYtURcH1ofFK+5bjxjF" | |
| + "F8Db4zoLyHBqUMrknjkLh84amDyysxhvalIbFNGTVq0mXwXpgeQ=="; | |
| CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); | |
| Certificate certificate = certificateFactory.generateCertificate( | |
| new ByteArrayInputStream(Base64.getDecoder().decode(certificateBase64))); | |
| Certificate[] certificateChain = { certificate }; | |
| char[] password = "password".toCharArray(); | |
| KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); | |
| keyStore.load(null, password); | |
| keyStore.setKeyEntry("private", privateKey, password, certificateChain); | |
| keyStore.setCertificateEntry("cert", certificate); | |
| KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( | |
| KeyManagerFactory.getDefaultAlgorithm()); | |
| keyManagerFactory.init(keyStore, password); | |
| TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( | |
| TrustManagerFactory.getDefaultAlgorithm()); | |
| trustManagerFactory.init(keyStore); | |
| SSLContext sslContext = SSLContext.getInstance("TLS"); | |
| sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), | |
| new SecureRandom()); | |
| return sslContext; | |
| } | |
| void test() throws Exception { | |
| Server server = new Server(); | |
| new Thread(server).start(); | |
| SSLSocketFactory socketFactory = sslContext().getSocketFactory(); | |
| for (int i = 0; i < 3; i++) { | |
| System.out.println(i); | |
| Socket socket = new Socket("localhost", server.serverSocket.getLocalPort()); | |
| SSLSocket sslSocket = (SSLSocket) socketFactory.createSocket( | |
| socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true); | |
| ClientSocketHandler clientSocketHandler = new ClientSocketHandler(sslSocket); | |
| clientSocketHandler.run(); | |
| } | |
| server.serverSocket.close(); | |
| } | |
| class Server implements Runnable { | |
| final ServerSocket serverSocket; | |
| Server() throws IOException { | |
| serverSocket = ServerSocketFactory.getDefault().createServerSocket(0); | |
| } | |
| @Override public void run() { | |
| try { | |
| SSLSocketFactory socketFactory = sslContext().getSocketFactory(); | |
| while (true) { | |
| Socket socket = serverSocket.accept(); | |
| SSLSocket sslSocket = (SSLSocket) socketFactory.createSocket( | |
| socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true); | |
| new Thread(new ServerSocketHandler(sslSocket)).start(); | |
| } | |
| } catch (SocketException e) { | |
| System.out.println("[S] shutdown: " + e.getMessage()); | |
| } catch (Exception e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| } | |
| class ClientSocketHandler implements Runnable, ALPN.ClientProvider { | |
| final SSLSocket socket; | |
| String protocol = "none"; | |
| public ClientSocketHandler(SSLSocket socket) { | |
| this.socket = socket; | |
| } | |
| @Override public void run() { | |
| try { | |
| ALPN.put(socket, this); | |
| socket.startHandshake(); | |
| BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); | |
| String s = reader.readLine(); | |
| System.out.println("[C] read: " + s + " using protocol " + protocol); | |
| socket.close(); | |
| } catch (Exception e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| @Override public boolean supports() { | |
| System.out.println("[C] " + socket + " supports ALPN"); | |
| return true; | |
| } | |
| @Override public List<String> protocols() { | |
| List<String> protocols = Arrays.asList("foo", "bar"); | |
| System.out.println("[C] " + socket + " protocols: " + protocols); | |
| return protocols; | |
| } | |
| @Override public void unsupported() { | |
| protocol = "unsupported"; | |
| ALPN.remove(socket); | |
| System.out.println("[C] " + socket + " unsupported"); | |
| } | |
| @Override public void selected(String protocol) { | |
| this.protocol = protocol; | |
| ALPN.remove(socket); | |
| System.out.println("[C] " + socket + " selected " + protocol); | |
| } | |
| } | |
| static class ServerSocketHandler implements Runnable, ALPN.ServerProvider { | |
| final SSLSocket socket; | |
| String protocol = "none"; | |
| public ServerSocketHandler(SSLSocket socket) { | |
| this.socket = socket; | |
| } | |
| @Override public void run() { | |
| try { | |
| socket.setUseClientMode(false); | |
| ALPN.put(socket, this); | |
| socket.startHandshake(); | |
| socket.getOutputStream().write(protocol.getBytes()); | |
| socket.getOutputStream().close(); | |
| socket.close(); | |
| } catch (Exception e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| @Override public void unsupported() { | |
| protocol = "unsupported"; | |
| System.out.println("[S] " + socket + " unsupported"); | |
| ALPN.remove(socket); | |
| } | |
| @Override public String select(List<String> list) { | |
| protocol = list.get(0); | |
| System.out.println("[S] " + socket + " selected " + protocol + " from " + list); | |
| ALPN.remove(socket); | |
| return protocol; | |
| } | |
| } | |
| public static void main(String[] args) throws Exception { | |
| new SessionResumeFail().test(); | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Executing this with java version "1.8.0_25" and ALPN 8.0.0.v20140317. Fixed in more recent versions of ALPN.