Skip to content

Commit

Permalink
QACI-406 Better compatibility detection
Browse files Browse the repository at this point in the history
- can run with any compatible implementation, not only NPN Bootstrap
- with new JDK versions after 8u250 uses it's JSSE impl by default
- still can use older NPN bootstrap versions if configured
- can use also other implementations (openjsse)
  • Loading branch information
dmatej committed Sep 7, 2020
1 parent dbc3f1a commit 09c8e3e
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 80 deletions.
2 changes: 1 addition & 1 deletion modules/http2/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@

<profiles>
<profile>
<id>opensse</id>
<id>openjsse</id>
<properties>
<bootClasspath>-Xbootclasspath/p:${settings.localRepository}/org/openjsse/openjsse/${openjsse.version}/openjsse-${openjsse.version}.jar</bootClasspath>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiFunction;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -43,37 +42,18 @@

/**
* Grizzly TLS Next Protocol Negotiation support class.
*
*/
public class AlpnSupport {
private final static Logger LOGGER = Grizzly.logger(AlpnSupport.class);
private static final Logger LOGGER = Grizzly.logger(AlpnSupport.class);

private final static Map<SSLEngine, Connection<?>> SSL_TO_CONNECTION_MAP =
new WeakHashMap<>();

private static final Map<SSLEngine, Connection<?>> SSL_TO_CONNECTION_MAP = new WeakHashMap<>();
private static final AlpnSupport INSTANCE;
private static final Method nativeHandshakeMethod;

static {
boolean isExtensionFound = false;
Method setHandshakeAlpnSelector = null;

try {
setHandshakeAlpnSelector = SSLEngine.class.getMethod("setHandshakeApplicationProtocolSelector", BiFunction.class);
} catch (Exception e) {
try {
ClassLoader.getSystemClassLoader().loadClass("sun.security.ssl.GrizzlyNPN");
isExtensionFound = true;
} catch (Exception e2) {
LOGGER.log(Level.FINE, "Native ALPN is not found:", e);
LOGGER.log(Level.FINE, "TLS ALPN extension is not found:", e2);
}
}
private static final AplnExtensionCompatibility COMPATIBILITY;

nativeHandshakeMethod = setHandshakeAlpnSelector;
INSTANCE = isExtensionFound
|| nativeHandshakeMethod != null
? new AlpnSupport() : null;
static {
COMPATIBILITY = AplnExtensionCompatibility.getInstance();
LOGGER.config(() -> "Detected ALPN compatibility info: " + COMPATIBILITY);
INSTANCE = COMPATIBILITY.isAlpnExtensionAvailable() ? new AlpnSupport() : null;
}

public static boolean isEnabled() {
Expand All @@ -84,7 +64,6 @@ public static AlpnSupport getInstance() {
if (!isEnabled()) {
throw new IllegalStateException("TLS ALPN is disabled");
}

return INSTANCE;
}

Expand All @@ -93,50 +72,52 @@ public static Connection<?> getConnection(final SSLEngine engine) {
return SSL_TO_CONNECTION_MAP.get(engine);
}
}

private static void setConnection(final SSLEngine engine,
final Connection<?> connection) {

private static void setConnection(final SSLEngine engine, final Connection<?> connection) {
synchronized (SSL_TO_CONNECTION_MAP) {
SSL_TO_CONNECTION_MAP.put(engine, connection);
}
}

private final Map<Object, AlpnServerNegotiator> serverSideNegotiators =
new WeakHashMap<>();
private final Map<Object, AlpnServerNegotiator> serverSideNegotiators = new WeakHashMap<>();
private final ReadWriteLock serverSideLock = new ReentrantReadWriteLock();

private final Map<Object, AlpnClientNegotiator> clientSideNegotiators =
new WeakHashMap<>();

private final Map<Object, AlpnClientNegotiator> clientSideNegotiators = new WeakHashMap<>();
private final ReadWriteLock clientSideLock = new ReentrantReadWriteLock();

private final HandshakeListener handshakeListener =
new HandshakeListener() {
private final HandshakeListener handshakeListener = new HandshakeListener() {

@Override
public void onInit(final Connection<?> connection, final SSLEngine sslEngine) {
assert sslEngine != null;

AlpnServerNegotiator negotiator = getServerNegotiator(connection);

if (negotiator != null && nativeHandshakeMethod != null) {
// Code only works for JDK9+
// sslEngine.setHandshakeApplicationProtocolSelector(negotiator);
try {
nativeHandshakeMethod.invoke(sslEngine, negotiator);
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, "Couldn't execute sslEngine.setHandshakeApplicationProtocolSelector", ex);
}
if (sslEngine.getUseClientMode()) {
// makes sense only for the server
return;
}
if (!COMPATIBILITY.isProtocolSelectorSetterInImpl()) {
// even when the api implements it, impl doesn't
return;
}
final AlpnServerNegotiator negotiator = getServerNegotiator(connection);
if (negotiator == null) {
return;
}
// Older JDK8 versions are missing this method in API, that's why we do this.
final Method setter = COMPATIBILITY.getProtocolSelectorSetter(sslEngine);
try {
setter.invoke(sslEngine, negotiator);
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, "Couldn't execute " + setter, ex);
}
}

@Override
public void onStart(final Connection<?> connection) {
final SSLEngine sslEngine = SSLUtils.getSSLEngine(connection);
assert sslEngine != null;

if (sslEngine.getUseClientMode()) {
AlpnClientNegotiator negotiator = getClientNegotiator(connection);

if (negotiator != null) {
// add a CloseListener to ensure we remove the
// negotiator associated with this SSLEngine
Expand All @@ -152,9 +133,7 @@ public void onClosed(Closeable closeable, CloseType type) throws IOException {
}
} else {
AlpnServerNegotiator negotiator = getServerNegotiator(connection);

if (negotiator != null) {

// add a CloseListener to ensure we remove the
// negotiator associated with this SSLEngine
connection.addCloseListener(new CloseListener<Closeable, CloseType>() {
Expand All @@ -168,7 +147,7 @@ public void onClosed(Closeable closeable, CloseType type) throws IOException {
NegotiationSupport.addNegotiator(sslEngine, negotiator);
}
}

}

@Override
Expand All @@ -179,48 +158,41 @@ public void onComplete(final Connection<?> connection) {
public void onFailure(Connection<?> connection, Throwable t) {
}
};

private AlpnSupport() {
}
}

public void configure(final SSLBaseFilter sslFilter) {
sslFilter.addHandshakeListener(handshakeListener);
}

public void setServerSideNegotiator(final Transport transport,
final AlpnServerNegotiator negotiator) {

public void setServerSideNegotiator(final Transport transport, final AlpnServerNegotiator negotiator) {
putServerSideNegotiator(transport, negotiator);
}

public void setServerSideNegotiator(final Connection<?> connection,
final AlpnServerNegotiator negotiator) {

public void setServerSideNegotiator(final Connection<?> connection, final AlpnServerNegotiator negotiator) {
putServerSideNegotiator(connection, negotiator);
}


public void setClientSideNegotiator(final Transport transport,
final AlpnClientNegotiator negotiator) {

public void setClientSideNegotiator(final Transport transport, final AlpnClientNegotiator negotiator) {
putClientSideNegotiator(transport, negotiator);
}

public void setClientSideNegotiator(final Connection<?> connection,
final AlpnClientNegotiator negotiator) {
public void setClientSideNegotiator(final Connection<?> connection, final AlpnClientNegotiator negotiator) {
putClientSideNegotiator(connection, negotiator);
}

private void putServerSideNegotiator(final Object object,
final AlpnServerNegotiator negotiator) {
private void putServerSideNegotiator(final Object object, final AlpnServerNegotiator negotiator) {
serverSideLock.writeLock().lock();

try {
serverSideNegotiators.put(object, negotiator);
} finally {
serverSideLock.writeLock().unlock();
}
}

private void putClientSideNegotiator(final Object object,
final AlpnClientNegotiator negotiator) {
private void putClientSideNegotiator(final Object object, final AlpnClientNegotiator negotiator) {
clientSideLock.writeLock().lock();

try {
Expand All @@ -229,12 +201,11 @@ private void putClientSideNegotiator(final Object object,
clientSideLock.writeLock().unlock();
}
}


private AlpnClientNegotiator getClientNegotiator(Connection<?> connection) {
AlpnClientNegotiator negotiator;
clientSideLock.readLock().lock();

try {
negotiator = clientSideNegotiators.get(connection);
if (negotiator == null) {
Expand All @@ -250,7 +221,6 @@ private AlpnClientNegotiator getClientNegotiator(Connection<?> connection) {
private AlpnServerNegotiator getServerNegotiator(Connection<?> connection) {
AlpnServerNegotiator negotiator;
serverSideLock.readLock().lock();

try {
negotiator = serverSideNegotiators.get(connection);
if (negotiator == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright (c) 2012, 2017 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.grizzly.http2;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.logging.Logger;

import javax.net.ssl.SSLEngine;

class AplnExtensionCompatibility {
private static final Logger LOG = Logger.getLogger(AplnExtensionCompatibility.class.getName());

private static final String IMPL_CLASS_NAME = "sun.security.ssl.SSLEngineImpl";
private static final String METHOD_NAME = "setHandshakeApplicationProtocolSelector";

private static AplnExtensionCompatibility INSTANCE;

private final boolean alpnExtensionGrizzly;
private final boolean protocolSelectorSetterInApi;
private final boolean protocolSelectorSetterInImpl;

public static synchronized AplnExtensionCompatibility getInstance() {
if (INSTANCE == null) {
INSTANCE = new AplnExtensionCompatibility();
}
return INSTANCE;
}


public boolean isAlpnExtensionAvailable() {
return isAlpnExtensionGrizzly() || isProtocolSelectorSetterInApi() || isProtocolSelectorSetterInImpl();
}


public boolean isAlpnExtensionGrizzly() {
return alpnExtensionGrizzly;
}


public boolean isProtocolSelectorSetterInApi() {
return protocolSelectorSetterInApi;
}


public boolean isProtocolSelectorSetterInImpl() {
return protocolSelectorSetterInImpl;
}


public Method getProtocolSelectorSetter(final SSLEngine engine) {
Objects.requireNonNull(engine, "engine");
try {
// newer JSSE versions implement this method.
// some JDK8 versions (Zulu 8u265) don't see the method as public on impl
final Class<? extends SSLEngine> engineClass;
if (isHandshakeSetterInApi()) {
engineClass = SSLEngine.class;
} else {
engineClass = engine.getClass();
}
return engineClass.getMethod(METHOD_NAME, BiFunction.class);
} catch (final NoSuchMethodException e) {
throw new IllegalArgumentException("The method public void setHandshakeApplicationProtocolSelector("
+ "BiFunction<SSLEngine, List<String>, String> selector) is not declared by"
+ " the " + engine.getClass().getName() + ".", e);
}
}


private AplnExtensionCompatibility() {
this.alpnExtensionGrizzly = isClassAvailableOnBootstrapClasspath("sun.security.ssl.GrizzlyNPN");
this.protocolSelectorSetterInApi = isHandshakeSetterInApi();
this.protocolSelectorSetterInImpl = isHandshakeSetterInImpl();
}


private static boolean isClassAvailableOnBootstrapClasspath(final String className) {
try {
ClassLoader.getSystemClassLoader().loadClass(className);
return true;
} catch (final ClassNotFoundException e) {
LOG.config("The class with the name '" + className + "' is not available on the bootstrap classpath.");
return false;
}
}


private static boolean isHandshakeSetterInImpl() {
try {
Class.forName(IMPL_CLASS_NAME).getMethod(METHOD_NAME, BiFunction.class);
return true;
} catch (final IllegalAccessError e) {
LOG.config(() -> "The class " + IMPL_CLASS_NAME + " is not accessible.");
return false;
} catch (final ClassNotFoundException | NoClassDefFoundError e) {
LOG.config(() -> "The class " + IMPL_CLASS_NAME + " cloud not be found.");
return false;
} catch (final NoSuchMethodException e) {
LOG.config(() -> "The method public void setHandshakeApplicationProtocolSelector("
+ "BiFunction<SSLEngine, List<String>, String> selector) is not declared by"
+ " the " + IMPL_CLASS_NAME + " class.");
return false;
}
}


private static boolean isHandshakeSetterInApi() {
try {
// new grizzly bootstrap versions implement this method.
SSLEngine.class.getMethod(METHOD_NAME, BiFunction.class);
return true;
} catch (final NoSuchMethodException e) {
LOG.config("The method public void setHandshakeApplicationProtocolSelector("
+ "BiFunction<SSLEngine, List<String>, String> selector) is not declared by"
+ " the " + SSLEngine.class.getName() + ".");
return false;
}
}


@Override
public String toString() {
return super.toString() + "ALPN available: " + isAlpnExtensionAvailable()
+ ", ALPN is Grizzly: " + isAlpnExtensionGrizzly()
+ ", setHandshakeApplicationProtocolSelector in API: " + isProtocolSelectorSetterInApi()
+ ", setHandshakeApplicationProtocolSelector in impl: " + isProtocolSelectorSetterInImpl();
}
}
Loading

0 comments on commit 09c8e3e

Please sign in to comment.