--- old/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java 2017-10-19 17:47:11.168686460 -0300 +++ new/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java 2017-10-19 17:47:11.070686424 -0300 @@ -29,7 +29,9 @@ import java.nio.*; import java.security.*; import java.util.*; +import java.util.Map.Entry; import java.util.function.BiFunction; +import java.util.function.Consumer; import javax.crypto.BadPaddingException; @@ -237,6 +239,15 @@ private byte[] serverVerifyData; /* + * If anyone wants to get notified about handshake completions, + * they'll show up on this list. + */ + private HashMap + handshakeCompletedListeners; + + private HashMap handshakeListeners; + + /* * READ ME * READ ME * READ ME * READ ME * READ ME * READ ME * * IMPORTANT STUFF TO UNDERSTANDING THE SYNCHRONIZATION ISSUES. * READ ME * READ ME * READ ME * READ ME * READ ME * READ ME * @@ -424,6 +435,21 @@ if (connectionState == cs_START) { connectionState = cs_HANDSHAKE; } else { // cs_DATA + if (handshakeListeners != null) { + for (final Entry e : + handshakeListeners.entrySet()) { + boolean renegotiationEnabled = AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Boolean run() { + return e.getKey().renegotiationEnabled(); + } + }, e.getValue()); + if (!renegotiationEnabled) { + return; + } + } + } connectionState = cs_RENEGOTIATE; } @@ -622,7 +648,7 @@ // // Kickstart handshake state machine if we need to ... // - if (!handshaker.activated()) { + if (handshaker != null && !handshaker.activated()) { // prior to handshaking, activate the handshake if (connectionState == cs_RENEGOTIATE) { // don't use SSLv2Hello when renegotiating @@ -1054,6 +1080,10 @@ * in it. */ initHandshaker(); + if (connectionState == cs_DATA && handshaker == null) { + // Renegotiation was disabled by a HandshakeListener + break; + } if (!handshaker.activated()) { // prior to handshaking, activate the handshake if (connectionState == cs_RENEGOTIATE) { @@ -1098,8 +1128,39 @@ connectionState = cs_DATA; } - // No handshakeListeners here. That's a - // SSLSocket thing. + if (handshakeListeners != null) { + HandshakeVerifyDataEvent event = + new HandshakeVerifyDataEvent(this, + clientVerifyData, + serverVerifyData); + + Thread thread = new Thread( + null, + new NotifyHandshakeVerifyData( + handshakeListeners.entrySet(), event), + "HandshakeNotify-Thread", + 0, + false); + thread.start(); + } + + // + // Tell folk about handshake completion, but do + // it in a separate thread. + // + if (handshakeCompletedListeners != null) { + HandshakeCompletedEvent event = + new HandshakeCompletedEvent(this, sess); + + Thread thread = new Thread( + null, + new NotifyHandshakeComplete( + handshakeCompletedListeners.entrySet(), event), + "HandshakeCompletedNotify-Thread", + 0, + false); + thread.start(); + } } else if (handshaker.taskOutstanding()) { hsStatus = HandshakeStatus.NEED_TASK; } @@ -1697,6 +1758,65 @@ } /** + * Registers an event listener to receive notifications that an + * SSL handshake has completed on this connection. + */ + @Override + public synchronized void addHandshakeCompletedListener( + HandshakeCompletedListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener is null"); + } + if (handshakeCompletedListeners == null) { + handshakeCompletedListeners = new + HashMap(4); + } + handshakeCompletedListeners.put(listener, AccessController.getContext()); + } + + /** + * Removes a previously registered handshake completion listener. + */ + @Override + public synchronized void removeHandshakeCompletedListener( + HandshakeCompletedListener listener) { + if (handshakeCompletedListeners == null) { + throw new IllegalArgumentException("no listeners"); + } + if (handshakeCompletedListeners.remove(listener) == null) { + throw new IllegalArgumentException("listener not registered"); + } + if (handshakeCompletedListeners.isEmpty()) { + handshakeCompletedListeners = null; + } + } + + @Override + public synchronized void addHandshakeListener(HandshakeListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener is null"); + } + if (handshakeListeners == null) { + handshakeListeners = new + HashMap(4); + } + handshakeListeners.put(listener, AccessController.getContext()); + } + + @Override + public synchronized void removeHandshakeListener(HandshakeListener listener) { + if (handshakeListeners == null) { + throw new IllegalArgumentException("no listeners"); + } + if (handshakeListeners.remove(listener) == null) { + throw new IllegalArgumentException("listener not registered"); + } + if (handshakeListeners.isEmpty()) { + handshakeListeners = null; + } + } + + /** * Returns a delegated Runnable task for * this SSLEngine. */ @@ -2286,6 +2406,72 @@ return this.applicationProtocolSelector; } + // + // We allocate a separate thread to deliver handshake completion + // events. This ensures that the notifications don't block the + // protocol state machine. + // + private static abstract class NotifyHandshakeBase implements Runnable { + + private Set> + targets; // who gets notified + private E event; // the notification + + NotifyHandshakeBase( + Set> + entrySet, E e) { + + targets = new HashSet<>(entrySet); // clone the entry set + event = e; + } + + @Override + public void run() { + // Don't need to synchronize, as it only runs in one thread. + for (Map.Entry + entry : targets) { + + final T l = entry.getKey(); + AccessControlContext acc = entry.getValue(); + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + getEventCallbackFunction(l).accept(event); + return null; + } + }, acc); + } + } + + protected abstract Consumer getEventCallbackFunction(T l); + } + + private static class NotifyHandshakeComplete extends + NotifyHandshakeBase { + + NotifyHandshakeComplete(Set> entrySet, + HandshakeCompletedEvent e) { + super(entrySet, e); + } + + protected Consumer getEventCallbackFunction(HandshakeCompletedListener l) { + return (HandshakeCompletedEvent e) -> l.handshakeCompleted(e); + } + } + + private static class NotifyHandshakeVerifyData extends + NotifyHandshakeBase { + + NotifyHandshakeVerifyData(Set> entrySet, + HandshakeVerifyDataEvent e) { + super(entrySet, e); + } + + protected Consumer getEventCallbackFunction(HandshakeListener l) { + return (HandshakeVerifyDataEvent e) -> l.finishedVerifyData(e); + } + } + /** * Returns a printable representation of this end of the connection. */