Skip to content

Commit

Permalink
Merge 8a71766 into a54ff9c
Browse files Browse the repository at this point in the history
  • Loading branch information
ericgribkoff committed Jan 19, 2018
2 parents a54ff9c + 8a71766 commit 47b489f
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 171 deletions.
32 changes: 29 additions & 3 deletions SECURITY.md
Expand Up @@ -10,12 +10,38 @@ On Android, use the [Play Services Provider](#tls-on-android). For non-Android s

## TLS on Android

On Android we recommend the use of the [Play Services Dynamic Security Provider](http://appfoundry.be/blog/2014/11/18/Google-Play-Services-Dynamic-Security-Provider) to ensure your application has an up-to-date OpenSSL library with the necessary ciper-suites and a reliable ALPN implementation.

You may need to [update the security provider](https://developer.android.com/training/articles/security-gms-provider.html) to enable ALPN support, especially for Android versions < 5.0. If the provider fails to update, ALPN may not work.
On Android we recommend the use of the [Play Services Dynamic Security
Provider](https://www.appfoundry.be/blog/2014/11/18/Google-Play-Services-Dynamic-Security-Provider/)
to ensure your application has an up-to-date OpenSSL library with the necessary
ciper-suites and a reliable ALPN implementation. This requires [updating the
security provider at
runtime](https://developer.android.com/training/articles/security-gms-provider.html).

Although ALPN mostly works on newer Android releases (especially since 5.0),
there are bugs and discovered security vulnerabilities that are only fixed by
upgrading the security provider. Thus, we recommend using the Play Service
Dynamic Security Provider for all Android versions.

*Note: The Dynamic Security Provider must be installed **before** creating a gRPC OkHttp channel. gRPC's OkHttpProtocolNegotiator statically initializes the security protocol(s) available to gRPC, which means that changes to the security provider after the first channel is created will not be picked up by gRPC.*

### Bundling Conscrypt

If depending on Play Services is not an option for your app, then you may bundle
[Conscrypt](https://conscrypt.org) with your application. Binaries are available
on [Maven
Central](https://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.conscrypt%20a%3Aconscrypt-android).

Like the Play Services Dynamic Security Provider, you must still "install"
Conscrypt before use.

```java
import org.conscrypt.Conscrypt;
import java.security.Security;
...

Security.insertProviderAt(Conscrypt.newProvider(), 1);
```

## TLS with OpenSSL

This is currently the recommended approach for using gRPC over TLS (on non-Android systems).
Expand Down
63 changes: 8 additions & 55 deletions okhttp/src/main/java/io/grpc/okhttp/OkHttpProtocolNegotiator.java
Expand Up @@ -21,12 +21,11 @@
import com.google.common.annotations.VisibleForTesting;
import io.grpc.okhttp.internal.OptionalMethod;
import io.grpc.okhttp.internal.Platform;
import io.grpc.okhttp.internal.Platform.TlsExtensionType;
import io.grpc.okhttp.internal.Protocol;
import io.grpc.okhttp.internal.Util;
import java.io.IOException;
import java.net.Socket;
import java.security.Provider;
import java.security.Security;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -42,7 +41,7 @@ class OkHttpProtocolNegotiator {
private static OkHttpProtocolNegotiator NEGOTIATOR =
createNegotiator(OkHttpProtocolNegotiator.class.getClassLoader());

private final Platform platform;
protected final Platform platform;

@VisibleForTesting
OkHttpProtocolNegotiator(Platform platform) {
Expand Down Expand Up @@ -73,7 +72,7 @@ static OkHttpProtocolNegotiator createNegotiator(ClassLoader loader) {
}
}
return android
? new AndroidNegotiator(DEFAULT_PLATFORM, AndroidNegotiator.DEFAULT_TLS_EXTENSION_TYPE)
? new AndroidNegotiator(DEFAULT_PLATFORM)
: new OkHttpProtocolNegotiator(DEFAULT_PLATFORM);
}

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

private static final TlsExtensionType DEFAULT_TLS_EXTENSION_TYPE =
pickTlsExtensionType(AndroidNegotiator.class.getClassLoader());

enum TlsExtensionType {
ALPN_AND_NPN,
NPN,
}

private final TlsExtensionType tlsExtensionType;

AndroidNegotiator(Platform platform, TlsExtensionType tlsExtensionType) {
AndroidNegotiator(Platform platform) {
super(platform);
this.tlsExtensionType = checkNotNull(tlsExtensionType, "Unable to pick a TLS extension");
}

@Override
Expand Down Expand Up @@ -175,11 +163,11 @@ protected void configureTlsExtensions(
}

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

if (tlsExtensionType != null) {
if (platform.getTlsExtensionType() != TlsExtensionType.NONE) {
SET_NPN_PROTOCOLS.invokeWithoutCheckedException(sslSocket, parameters);
} else {
throw new RuntimeException("We can not do TLS handshake on this Android version, please"
Expand All @@ -189,7 +177,7 @@ protected void configureTlsExtensions(

@Override
public String getSelectedProtocol(SSLSocket socket) {
if (tlsExtensionType == TlsExtensionType.ALPN_AND_NPN) {
if (platform.getTlsExtensionType() == TlsExtensionType.ALPN_AND_NPN) {
try {
byte[] alpnResult =
(byte[]) GET_ALPN_SELECTED_PROTOCOL.invokeWithoutCheckedException(socket);
Expand All @@ -202,7 +190,7 @@ public String getSelectedProtocol(SSLSocket socket) {
}
}

if (tlsExtensionType != null) {
if (platform.getTlsExtensionType() != TlsExtensionType.NONE) {
try {
byte[] npnResult =
(byte[]) GET_NPN_SELECTED_PROTOCOL.invokeWithoutCheckedException(socket);
Expand All @@ -216,40 +204,5 @@ public String getSelectedProtocol(SSLSocket socket) {
}
return null;
}

@VisibleForTesting
static TlsExtensionType pickTlsExtensionType(ClassLoader loader) {
// Decide which TLS Extension (APLN and NPN) we will use, follow the rules:
// 1. If Google Play Services Security Provider is installed, use both
// 2. If on Android 5.0 or later, use both, else
// 3. If on Android 4.1 or later, use NPN, else
// 4. Fail.
// TODO(madongfly): Logging.

// Check if Google Play Services Security Provider is installed.
Provider provider = Security.getProvider("GmsCore_OpenSSL");
if (provider != null) {
return TlsExtensionType.ALPN_AND_NPN;
}

// Check if on Android 5.0 or later.
try {
loader.loadClass("android.net.Network"); // Arbitrary class added in Android 5.0.
return TlsExtensionType.ALPN_AND_NPN;
} catch (ClassNotFoundException e) {
logger.log(Level.FINE, "Can't find class", e);
}

// Check if on Android 4.1 or later.
try {
loader.loadClass("android.app.ActivityOptions"); // Arbitrary class added in Android 4.1.
return TlsExtensionType.NPN;
} catch (ClassNotFoundException e) {
logger.log(Level.FINE, "Can't find class", e);
}

// This will be caught by the constructor.
return null;
}
}
}
Expand Up @@ -19,7 +19,6 @@
import static com.google.common.base.Charsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
Expand All @@ -28,17 +27,13 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import io.grpc.okhttp.OkHttpProtocolNegotiator.AndroidNegotiator;
import io.grpc.okhttp.OkHttpProtocolNegotiator.AndroidNegotiator.TlsExtensionType;
import io.grpc.okhttp.internal.Platform;
import io.grpc.okhttp.internal.Platform.TlsExtensionType;
import io.grpc.okhttp.internal.Protocol;
import java.io.IOException;
import java.security.Provider;
import java.security.Security;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
Expand All @@ -53,21 +48,9 @@
public class OkHttpProtocolNegotiatorTest {
@Rule public final ExpectedException thrown = ExpectedException.none();

private final Provider fakeSecurityProvider = new Provider("GmsCore_OpenSSL", 1.0, "info") {};
private final SSLSocket sock = mock(SSLSocket.class);
private final Platform platform = mock(Platform.class);

@Before
public void setUp() {
// Tests that depend on android need this to know which protocol negotiation to use.
Security.addProvider(fakeSecurityProvider);
}

@After
public void tearDown() {
Security.removeProvider(fakeSecurityProvider.getName());
}

@Test
public void createNegotiator_isAndroid() {
ClassLoader cl = new ClassLoader(this.getClass().getClassLoader()) {
Expand Down Expand Up @@ -179,78 +162,11 @@ public void negotiate_preferGrpcExp() throws Exception {
verify(platform).afterHandshake(sock);
}

@Test
public void pickTlsExtensionType_securityProvider() throws Exception {
assertNotNull(Security.getProvider(fakeSecurityProvider.getName()));

AndroidNegotiator.TlsExtensionType tlsExtensionType =
AndroidNegotiator.pickTlsExtensionType(getClass().getClassLoader());

assertEquals(TlsExtensionType.ALPN_AND_NPN, tlsExtensionType);
}

@Test
public void pickTlsExtensionType_android50() throws Exception {
Security.removeProvider(fakeSecurityProvider.getName());
ClassLoader cl = new ClassLoader(this.getClass().getClassLoader()) {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// Just don't throw.
if ("android.net.Network".equals(name)) {
return null;
}
return super.findClass(name);
}
};

AndroidNegotiator.TlsExtensionType tlsExtensionType =
AndroidNegotiator.pickTlsExtensionType(cl);

assertEquals(TlsExtensionType.ALPN_AND_NPN, tlsExtensionType);
}

@Test
public void pickTlsExtensionType_android41() throws Exception {
Security.removeProvider(fakeSecurityProvider.getName());
ClassLoader cl = new ClassLoader(this.getClass().getClassLoader()) {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// Just don't throw.
if ("android.app.ActivityOptions".equals(name)) {
return null;
}
return super.findClass(name);
}
};

AndroidNegotiator.TlsExtensionType tlsExtensionType =
AndroidNegotiator.pickTlsExtensionType(cl);

assertEquals(TlsExtensionType.NPN, tlsExtensionType);
}

@Test
public void pickTlsExtensionType_none() throws Exception {
Security.removeProvider(fakeSecurityProvider.getName());

AndroidNegotiator.TlsExtensionType tlsExtensionType =
AndroidNegotiator.pickTlsExtensionType(getClass().getClassLoader());

assertNull(tlsExtensionType);
}

@Test
public void androidNegotiator_failsOnNull() {
thrown.expect(NullPointerException.class);
thrown.expectMessage("Unable to pick a TLS extension");

new AndroidNegotiator(platform, null);
}

// Checks that the super class is properly invoked.
@Test
public void negotiate_android_handshakeFails() throws Exception {
AndroidNegotiator negotiator = new AndroidNegotiator(platform, TlsExtensionType.ALPN_AND_NPN);
when(platform.getTlsExtensionType()).thenReturn(TlsExtensionType.ALPN_AND_NPN);
AndroidNegotiator negotiator = new AndroidNegotiator(platform);

FakeAndroidSslSocket androidSock = new FakeAndroidSslSocket() {
@Override
Expand All @@ -275,7 +191,8 @@ public byte[] getAlpnSelectedProtocol() {

@Test
public void getSelectedProtocol_alpn() throws Exception {
AndroidNegotiator negotiator = new AndroidNegotiator(platform, TlsExtensionType.ALPN_AND_NPN);
when(platform.getTlsExtensionType()).thenReturn(TlsExtensionType.ALPN_AND_NPN);
AndroidNegotiator negotiator = new AndroidNegotiator(platform);
FakeAndroidSslSocket androidSock = new FakeAndroidSslSocketAlpn();

String actual = negotiator.getSelectedProtocol(androidSock);
Expand All @@ -293,7 +210,8 @@ public byte[] getNpnSelectedProtocol() {

@Test
public void getSelectedProtocol_npn() throws Exception {
AndroidNegotiator negotiator = new AndroidNegotiator(platform, TlsExtensionType.NPN);
when(platform.getTlsExtensionType()).thenReturn(TlsExtensionType.NPN);
AndroidNegotiator negotiator = new AndroidNegotiator(platform);
FakeAndroidSslSocket androidSock = new FakeAndroidSslSocketNpn();

String actual = negotiator.getSelectedProtocol(androidSock);
Expand Down

0 comments on commit 47b489f

Please sign in to comment.