Skip to content
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aa9b7ec
Use new APIs for configuring TLS whenever possible.
voidzcy Apr 8, 2020
0e6e2d0
Ignore checkstyle warning for empty catch block
voidzcy Apr 8, 2020
8e0e641
Add test for verifying invoking getApplicationProtocol() for android …
voidzcy Apr 8, 2020
741247b
Fix a wrong reflection call.
voidzcy Apr 9, 2020
612225a
Add comment for explanation.
voidzcy Apr 9, 2020
b29cf26
Fix checking wrong protocol type.
voidzcy Apr 13, 2020
284c51e
Should still calls old version (reflective) methods in case of Conscr…
voidzcy Apr 14, 2020
745d447
Remove unused method.
voidzcy Apr 14, 2020
197c325
Should still call the old version when using platform socket while se…
voidzcy Apr 14, 2020
ccc3bbe
Clean up unused import
voidzcy Apr 14, 2020
cf373b5
Use public API whenever possible, only use old reflective API when pu…
voidzcy Apr 15, 2020
46db165
Use new API for setting SNI whenever possible.
voidzcy Apr 15, 2020
8e4e3a2
Better exception propagations.
voidzcy Apr 15, 2020
678bbc7
Format
voidzcy Apr 15, 2020
66f0b8e
Eliminate ignored exception.
voidzcy Apr 15, 2020
1fb9b20
Format
voidzcy Apr 15, 2020
1eca84a
Use normal Method for using public APIs.
voidzcy Apr 15, 2020
7d69401
Revert unit test for verifying invocation of getApplicationProtocols(…
voidzcy Apr 15, 2020
be1867e
Check if SSLSocket.getApplicationProtocol() is supported to determine…
voidzcy Apr 16, 2020
a5b7124
Fix exception catching for UnsupportedOperationException.
voidzcy Apr 16, 2020
03e81ae
No need to load classes, they are already publicly available.
voidzcy Apr 16, 2020
cc435c0
Add comment for checking if underlying provider supports enabling ALP…
voidzcy Apr 16, 2020
54eff36
Reword comment.
voidzcy Apr 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 154 additions & 5 deletions okhttp/src/main/java/io/grpc/okhttp/OkHttpProtocolNegotiator.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,18 @@
import io.grpc.okhttp.internal.Protocol;
import io.grpc.okhttp.internal.Util;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;

/**
Expand Down Expand Up @@ -133,6 +140,69 @@ static final class AndroidNegotiator extends OkHttpProtocolNegotiator {
private static final OptionalMethod<Socket> SET_NPN_PROTOCOLS =
new OptionalMethod<>(null, "setNpnProtocols", byte[].class);

// Non-null on Android 10.0+.
// SSLSockets.isSupportedSocket(SSLSocket)
private static final Method SSL_SOCKETS_IS_SUPPORTED_SOCKET;
// SSLSockets.setUseSessionTickets(SSLSocket, boolean)
private static final Method SSL_SOCKETS_SET_USE_SESSION_TICKET;
// SSLParameters.setApplicationProtocols(String[])
private static final Method SET_APPLICATION_PROTOCOLS;
// SSLParameters.getApplicationProtocols()
private static final Method GET_APPLICATION_PROTOCOLS;
// SSLSocket.getApplicationProtocol()
private static final Method GET_APPLICATION_PROTOCOL;

// Non-null on Android 7.0+.
// SSLParameters.setServerNames(List<SNIServerName>)
private static final Method SET_SERVER_NAMES;
// SNIHostName(String)
private static final Constructor<?> SNI_HOST_NAME;

static {
// Attempt to find Android 10.0+ APIs.
Method setApplicationProtocolsMethod = null;
Method getApplicationProtocolsMethod = null;
Method getApplicationProtocolMethod = null;
Method sslSocketsIsSupportedSocketMethod = null;
Method sslSocketsSetUseSessionTicketsMethod = null;
try {
Class<?> sslParameters = SSLParameters.class;
setApplicationProtocolsMethod =
sslParameters.getMethod("setApplicationProtocols", String[].class);
getApplicationProtocolsMethod = sslParameters.getMethod("getApplicationProtocols");
getApplicationProtocolMethod = SSLSocket.class.getMethod("getApplicationProtocol");
Class<?> sslSockets = Class.forName("android.net.ssl.SSLSockets");
sslSocketsIsSupportedSocketMethod =
sslSockets.getMethod("isSupportedSocket", SSLSocket.class);
sslSocketsSetUseSessionTicketsMethod =
sslSockets.getMethod("setUseSessionTickets", SSLSocket.class, boolean.class);
} catch (ClassNotFoundException e) {
logger.log(Level.FINER, "Failed to find Android 10.0+ APIs", e);
} catch (NoSuchMethodException e) {
logger.log(Level.FINER, "Failed to find Android 10.0+ APIs", e);
}
SET_APPLICATION_PROTOCOLS = setApplicationProtocolsMethod;
GET_APPLICATION_PROTOCOLS = getApplicationProtocolsMethod;
GET_APPLICATION_PROTOCOL = getApplicationProtocolMethod;
SSL_SOCKETS_IS_SUPPORTED_SOCKET = sslSocketsIsSupportedSocketMethod;
SSL_SOCKETS_SET_USE_SESSION_TICKET = sslSocketsSetUseSessionTicketsMethod;

// Attempt to find Android 7.0+ APIs.
Method setServerNamesMethod = null;
Constructor<?> sniHostNameConstructor = null;
try {
setServerNamesMethod = SSLParameters.class.getMethod("setServerNames", List.class);
sniHostNameConstructor =
Class.forName("javax.net.ssl.SNIHostName").getConstructor(String.class);
} catch (ClassNotFoundException e) {
logger.log(Level.FINER, "Failed to find Android 7.0+ APIs", e);
} catch (NoSuchMethodException e) {
logger.log(Level.FINER, "Failed to find Android 7.0+ APIs", e);
}
SET_SERVER_NAMES = setServerNamesMethod;
SNI_HOST_NAME = sniHostNameConstructor;
}

AndroidNegotiator(Platform platform) {
super(platform);
}
Expand All @@ -152,21 +222,75 @@ public String negotiate(SSLSocket sslSocket, String hostname, List<Protocol> pro
/**
* Override {@link Platform}'s configureTlsExtensions for Android older than 5.0, since OkHttp
* (2.3+) only support such function for Android 5.0+.
*
* <p>Note: Prior to Android Q, the standard way of accessing some Conscrypt features was to
* use reflection to call hidden APIs. Beginning in Q, there is public API for all of these
* features. We attempt to use the public API where possible. Otherwise, fall back to use the
* old reflective API.
*/
@Override
protected void configureTlsExtensions(
SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
// Enable SNI and session tickets.
if (hostname != null) {
SET_USE_SESSION_TICKETS.invokeOptionalWithoutCheckedException(sslSocket, true);
SET_HOSTNAME.invokeOptionalWithoutCheckedException(sslSocket, hostname);
String[] protocolNames = protocolIds(protocols);
SSLParameters sslParams = sslSocket.getSSLParameters();
try {
// Enable SNI and session tickets.
if (hostname != null) {
if (SSL_SOCKETS_IS_SUPPORTED_SOCKET != null
&& (boolean) SSL_SOCKETS_IS_SUPPORTED_SOCKET.invoke(null, sslSocket)) {
SSL_SOCKETS_SET_USE_SESSION_TICKET.invoke(null, sslSocket, true);
} else {
SET_USE_SESSION_TICKETS.invokeOptionalWithoutCheckedException(sslSocket, true);
}
if (SET_SERVER_NAMES != null && SNI_HOST_NAME != null) {
SET_SERVER_NAMES
.invoke(sslParams, Collections.singletonList(SNI_HOST_NAME.newInstance(hostname)));
} else {
SET_HOSTNAME.invokeOptionalWithoutCheckedException(sslSocket, hostname);
}
}
boolean alpnEnabled = false;
if (GET_APPLICATION_PROTOCOL != null) {
try {
// If calling SSLSocket.getApplicationProtocol() throws UnsupportedOperationException,
// the underlying provider does not implement operations for enabling
// ALPN in the fashion of SSLParameters.setApplicationProtocols(). Fall back to
// use old hidden methods.
GET_APPLICATION_PROTOCOL.invoke(sslSocket);
SET_APPLICATION_PROTOCOLS.invoke(sslParams, (Object) protocolNames);
alpnEnabled = true;
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
if (targetException instanceof UnsupportedOperationException) {
logger.log(Level.FINER, "setApplicationProtocol unsupported, will try old methods");
} else {
throw e;
}
}
}
sslSocket.setSSLParameters(sslParams);
// Check application protocols are configured correctly. If not, configure again with
// old methods.
// Workaround for Conscrypt bug: https://github.com/google/conscrypt/issues/832
if (alpnEnabled && GET_APPLICATION_PROTOCOLS != null) {
String[] configuredProtocols =
(String[]) GET_APPLICATION_PROTOCOLS.invoke(sslSocket.getSSLParameters());
if (Arrays.equals(protocolNames, configuredProtocols)) {
return;
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}

Object[] parameters = {Platform.concatLengthPrefixed(protocols)};
if (platform.getTlsExtensionType() == TlsExtensionType.ALPN_AND_NPN) {
SET_ALPN_PROTOCOLS.invokeWithoutCheckedException(sslSocket, parameters);
}

if (platform.getTlsExtensionType() != TlsExtensionType.NONE) {
SET_NPN_PROTOCOLS.invokeWithoutCheckedException(sslSocket, parameters);
} else {
Expand All @@ -177,6 +301,23 @@ protected void configureTlsExtensions(

@Override
public String getSelectedProtocol(SSLSocket socket) {
if (GET_APPLICATION_PROTOCOL != null) {
try {
return (String) GET_APPLICATION_PROTOCOL.invoke(socket);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
if (targetException instanceof UnsupportedOperationException) {
logger.log(
Level.FINER,
"Socket unsupported for getApplicationProtocol, will try old methods");
} else {
throw new RuntimeException(e);
}
}
}

if (platform.getTlsExtensionType() == TlsExtensionType.ALPN_AND_NPN) {
try {
byte[] alpnResult =
Expand Down Expand Up @@ -207,4 +348,12 @@ public String getSelectedProtocol(SSLSocket socket) {
return null;
}
}

private static String[] protocolIds(List<Protocol> protocols) {
List<String> result = new ArrayList<>();
for (Protocol protocol : protocols) {
result.add(protocol.toString());
}
return result.toArray(new String[0]);
}
}