Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

okhttp: support Conscrypt security provider #3971

Merged
merged 2 commits into from
Jan 31, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
32 changes: 29 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
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.addProvider(Conscrypt.newProvider());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should prefer insertProviderAt so that Conscrypt can be prioritized.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

```

## TLS with OpenSSL

This is currently the recommended approach for using gRPC over TLS (on non-Android systems).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
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;
Expand Down Expand Up @@ -221,14 +220,17 @@ public String getSelectedProtocol(SSLSocket socket) {
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.
// 2. If Conscrypt is installed, use both
// 3. If on Android 5.0 or later, use both, else
// 4. If on Android 4.1 or later, use NPN, else
// 5. Fail.

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

if (Security.getProvider("Conscrypt") != null) {
return TlsExtensionType.ALPN_AND_NPN;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public class OkHttpProtocolNegotiatorTest {
@Rule public final ExpectedException thrown = ExpectedException.none();

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

Expand Down Expand Up @@ -229,6 +230,31 @@ protected Class<?> findClass(String name) throws ClassNotFoundException {
assertEquals(TlsExtensionType.NPN, tlsExtensionType);
}

@Test
public void pickTlsExtensionType_android41WithConscrypt() throws Exception {
Security.removeProvider(fakeSecurityProvider.getName());
Security.addProvider(fakeConscrypt);
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.ALPN_AND_NPN, tlsExtensionType);

// Clean up
Security.removeProvider(fakeConscrypt.getName());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try-finally or similar (could maybe remove it unconditionally in the @After).

}

@Test
public void pickTlsExtensionType_none() throws Exception {
Security.removeProvider(fakeSecurityProvider.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import okio.Buffer;
Expand All @@ -63,14 +62,26 @@ public class Platform {
public static final Logger logger = Logger.getLogger(Platform.class.getName());

/**
* List of security providers to use in order of preference.
* List of preferred security provider names, in order of preference. These will be used, if
* present, followed by {@code SECONDARY_ANDROID_SECURITY_PROVIDER_CLASSES} if these are
* unavailable.
*/
private static final String[] ANDROID_SECURITY_PROVIDERS =
new String[] {
// See https://developer.android.com/training/articles/security-gms-provider.html
"GmsCore_OpenSSL",
"Conscrypt"
};
/**
* List of secondary security provider class names to use in order of preference, if the
* preferred providers are unavailable.
*/
private static final String[] ANDROID_SECURITY_PROVIDERS = new String[]{
// See https://developer.android.com/training/articles/security-gms-provider.html
"com.google.android.gms.org.conscrypt.OpenSSLProvider",
"com.android.org.conscrypt.OpenSSLProvider",
"org.conscrypt.OpenSSLProvider",
"org.apache.harmony.xnet.provider.jsse.OpenSSLProvider"};
private static final String[] SECONDARY_ANDROID_SECURITY_PROVIDER_CLASSES =
new String[] {
"com.android.org.conscrypt.OpenSSLProvider",
"org.conscrypt.OpenSSLProvider",
"org.apache.harmony.xnet.provider.jsse.OpenSSLProvider"
};

private static final Platform PLATFORM = findPlatform();

Expand Down Expand Up @@ -199,11 +210,18 @@ private static Provider getAppEngineProvider() {
}

/**
* Select from the available security providers in preference order. If a preferred provider
* is not found then warn but continue.
* Select from the available security providers in preference order. If a preferred provider is
* not found then warn but continue.
*/
private static Provider getAndroidSecurityProvider() {
for (String providerClassName : ANDROID_SECURITY_PROVIDERS) {
for (String providerName : ANDROID_SECURITY_PROVIDERS) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should loop through the providers in order and choose the first we can use. That way the user has some way of configuring our behavior, and our behavior will better match expectations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Provider provider = Security.getProvider(providerName);
if (provider != null) {
logger.log(Level.FINE, "Found registered provider {0}", provider);
return provider;
}
}
for (String providerClassName : SECONDARY_ANDROID_SECURITY_PROVIDER_CLASSES) {
Provider[] providers = Security.getProviders();
for (Provider availableProvider : providers) {
if (providerClassName.equals(availableProvider.getClass().getName())) {
Expand Down