grpc / grpc-java

The Java gRPC implementation. HTTP/2 based RPC
https://grpc.io/docs/languages/java/
Apache License 2.0
11.43k stars 3.85k forks source link

Question on Android Requirements #1906

Closed emojahedi closed 8 years ago

emojahedi commented 8 years ago

I have a grpc server written in go which serves through a valid https certificate, and an Android client like the examples but without the usePlaintext(true) part.

The RPC services work fine for Android devices with sdk-version >= 21, but they fail for older devices with this exception:

io.grpc.StatusRuntimeException: UNAVAILABLE: No provided cause
    at io.grpc.Status.asRuntimeException(Status.java:503)
    at io.grpc.stub.ClientCalls.getUnchecked(ClientCalls.java:207)
    at io.grpc.stub.ClientCalls.blockingUnaryCall(ClientCalls.java:140)
    at net.honarnama.nano.AuthServiceGrpc$AuthServiceBlockingStub.createAccount(AuthServiceGrpc.java:290)
    ... app stack
Caused by: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb8adea90: Failure in SSL library, usually a protocol error
error:140740B5:SSL routines:SSL23_CLIENT_HELLO:no ciphers available (external/openssl/ssl/s23_clnt.c:486 0xac7ba990:0x00000000)
    at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:448)
    at io.grpc.okhttp.OkHttpProtocolNegotiator.negotiate(OkHttpProtocolNegotiator.java:106)
    at io.grpc.okhttp.OkHttpProtocolNegotiator$AndroidNegotiator.negotiate(OkHttpProtocolNegotiator.java:172)
    at io.grpc.okhttp.OkHttpTlsUpgrader.upgrade(OkHttpTlsUpgrader.java:74)
    at io.grpc.okhttp.OkHttpClientTransport$1.run(OkHttpClientTransport.java:345)
    at io.grpc.internal.SerializingExecutor$TaskRunner.run(SerializingExecutor.java:154)
    ... 3 more
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb8adea90: Failure in SSL library, usually a protocol error
error:140740B5:SSL routines:SSL23_CLIENT_HELLO:no ciphers available (external/openssl/ssl/s23_clnt.c:486 0xac7ba990:0x00000000)
    at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
    at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:405)
    ... 8 more

Using this mechanism I was able to perform the rpc call on an older device, but the suitable Google Play Services version isn't installed on more than half of the devices in my target population. Is there another way to make grpc work on theses devices? Like switching to Netty-based transport? Is there any sample of an android app usig Netty-based transport?

Thanks in advance

ejona86 commented 8 years ago

Using the Google Play Services Security Provider is pretty necessary on older devices in order to have ALPN support and be less buggy. I'd recommend using it even on newer devices, because it contains bug fixes.

The provider is https://conscrypt.org/, which you could compile yourself and include in your app. But you will then be shipping BoringSSL with your app which will increase app size.

Netty doesn't help because it would still require a recent TLS implementation.

emojahedi commented 8 years ago

Thank you it was a great help.

ejona86 commented 8 years ago

And I should note that we are working on a cronet-based transport (as an alternative to OkHttp, mainly for QUIC support). It would always include its own copy of boringssl, so it would not depend on Google Play Services Security Provider.

clockzhong commented 8 years ago

I got the same error. I used Python to implement my server. And the Python client could work correctly with it. But my Android client couldn't. Who could tell me why? Thanks!

clockzhong commented 8 years ago

Sorry, I've located the root cause for my issue: I didn't add user permission for my android application. Just addin the following line into AndroidManifest.xml could fix my issue:

<uses-permission android:name="android.permission.INTERNET" />
kraghu commented 7 years ago

@ericgribkoff I still see this exception on Android devices < 21. Is the cronet based transport is available ? Or Do I have to use the above mechanism using Google Play Services Security Provider ?

ericgribkoff commented 7 years ago

The Cronet transport is not yet available, mainly due to low prioritization. For devices < 21, you should use the Google Play Services Security Provider. If this isn't a viable solution for your target audience, let me know and I can look further into Cronet as an alternative - it's primarily a matter of investigating what it would take to get the Cronet dependencies integrated into gRPC's build system.

kraghu commented 7 years ago

@ericgribkoff I will get back to you on this by the end of this week

ejona86 commented 7 years ago

It's also possible to ship conscrypt.org as part of your app (it's the same thing that comes with Android and Google Play Services Security Provider). This used to mean compiling it yourself, but I now see a conscrypt-android aar artifact on Maven Central, so that may be a semi-easy alternative.

kraghu commented 7 years ago

@ejona86 thanks. Unfortunately it is gonna increaseo ur app size in an inefficient way. @ericgribkoff can you help us on getting cronet with smaller size ? or any alternative to get it working on <21 devices ?

ejona86 commented 7 years ago

Cronet should be strictly larger than conscrypt. Both include boringssl, and most of conscrypt's size is from boringssl.

ericgribkoff commented 7 years ago

I experimented with adding Cronet to our interop test app, and it actually clocks in just a little smaller than org.conscrypt:conscrypt-android:1.0.0.RC8. But it's still an increase of ~9-10MB.

Just spoke with @ejona86 and it's probably possible to get a smaller build of Conscrypt if you explore building it yourself. For example, netty-tcnative-boringssl-static includes boringssl and is only ~2.5MB. @kraghu if that kind of size increase is acceptable to your app, that's probably the best (and only) approach I can suggest here.

ejona86 commented 7 years ago

I just checked, stripping the Conscrypt binaries cuts their size by ⅘ths. I filed google/conscrypt#266. So I'm seeing proper Conscrypt binaries being at most ½ the size of Cronet binaries.

(Let it be known I'm not against Cronet. But by its nature it would be very strange for it to be smaller than Conscrypt.)

kraghu commented 7 years ago

@ejona86 @ericgribkoff It looks like google service's ProviderInstaller.installIfNeeded() wont help the grpc case at all. There is a note clearly stating this in the link

Caution: Updating a device's security Provider does not update android.net.SSLCertificateSocketFactory. Rather than using this class, we encourage app developers to use high-level methods for interacting with cryptography. Most apps can use APIs like HttpsURLConnection without needing to set a custom TrustManager or create an SSLCertificateSocketFactory.

Can GRPC android provides some way to set HttpsURLConnection using the right SSLCertificateSocketFactory in those cases as described in the note? This will be an easier solution for android and we can avoid increasing the app size by not including any library . This is how okhttp library handled this situation before

ericgribkoff commented 7 years ago

The quote just means not to use that class directly, expecting it to be updated. gRPC Android does the correct thing and uses the preferred provider on the device; this will handle TLS properly if ProviderInstaller.installIfNeeded() succeeds. Please see the app code for an example of this in action, although note that the test app's failure handling is not as robust as in the documentation link you provided - specifically, our test app does not try GooglePlayServicesUtil.showErrorDialogFragment, although it probably should.

As I understand it, this is the recommended way of handling security on older devices, and it's compatible with gRPC Android without any increase in APK size. Bundling Conscrypt was the suggested solution for environments where an up-to-date Google Play Services is not available.

ejona86 commented 7 years ago

I will also note that gRPC already allows you to specify your instance of SSLCertificateSocketFactory. Although, honestly, I'd discourage people from using it since virtually all recent bug reports with TLS on Android turned out to be caused by bugs in the app's factory (or the app's factory triggering bugs in the underlying libraries (not gRPC)).

kraghu commented 7 years ago

@ejona86 @ericgribkoff based on our last conversation I should either include io.netty:netty-tcnative-boringssl-static:2.0.5.Final or Conscrypt . You were going to strip down Conscrypt by half of its size . where can I get it ? Which one is recommended for <21 devices ? It looks like conscrypt is not on mavent yet.

ejona86 commented 7 years ago

There's a new RC release, 1.0.0.RC9, that doesn't include debug symbols. Consrypt is on Maven Central. Note that RC9 was very recently released and hasn't been indexed by search.maven.org yet. But it is available.

ejona86 commented 7 years ago

@kraghu, FYI, I just made #3301 that says how to configure Conscrypt at runtime.

kraghu commented 7 years ago

@ejona86 thank you so much. will try today

ericgribkoff commented 7 years ago

@kraghu FYI I encountered some issues getting the Conscrypt jar on Maven to run with Android API level 19 and below (tested 16 and 19). I've filed https://github.com/google/conscrypt/issues/276 to track this: I believe they may need to release a new version of the library to support these lower API levels.

kraghu commented 7 years ago

@ejona86 I still sees ssl error .I included Conscrpt and tried to provide it on run time . Is there any thing I am missing ?

EDITED: Oops i saw your message now @ericgribkoff .What should I do mean while . I also think it has to do with the way Android devices get manufacture upgrading. Although some upgraded devices say they are 19 but they are actually 14 or 15 and wont comply with google service upgrades and many other behaviors. This is pretty much a big problem in android with upgraded devices:(

ericgribkoff commented 7 years ago

@kraghu If you need something immediately, you'll have to compile Conscrypt yourself: there are instructions at https://github.com/google/conscrypt/blob/master/BUILDING.md

gengjiawen commented 7 years ago

Can we provide okhttp client to OkHttpChannelBuilder? I run this problem with glide too, provide the glide with okhttp client fix the https issue.

gengjiawen commented 7 years ago

I tried provide OkHttpChannelBuilder with sslSocketFactory using method in this url https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.Builder.html#sslSocketFactory-javax.net.ssl.SSLSocketFactory-javax.net.ssl.X509TrustManager-. But no luck.

gcjc commented 7 years ago

FYI if anyone struggles with this I got it to work on API 19 (after much pain and re-reading this thread). Two problems, nothing providing the correct algorithms (conscrypt solves) and API 19 using SSL2/3 by default:

build.gradle: compile 'org.conscrypt:conscrypt-android:1.0.0+'

Install provider, e.g. in MainActivity class: static { Security.insertProviderAt(Conscrypt.newProvider("GmsCore_OpenSSL"), 1); }

Add class TLSSocketFactory: From https://gist.github.com/fkrauthan/ac8624466a4dee4fd02f#file-tlssocketfactory-java

Build your channel as follow:

OkHttpChannelBuilder managedChannelBuilder = OkHttpChannelBuilder.forAddress(HOST, PORT);
managedChannelBuilder.sslSocketFactory(new TLSSocketFactory());
managedChannel = managedChannelBuilder.build();

You could of course do this in 1 line.

mrthemuradin commented 6 years ago

What worked for me to solve this issue on android < 21 - when you are creating channel for SpeechGrpc try to add this line to builder: .connectionSpec(ConnectionSpec.MODERN_TLS), so full code snippet looks like:

final ManagedChannel channel = new OkHttpChannelProvider()
                    .builderForAddress(HOSTNAME, PORT)
                    .connectionSpec(ConnectionSpec.MODERN_TLS)
                    .nameResolverFactory(new DnsNameResolverProvider())
                    .intercept(new GoogleCredentialsInterceptor(new GoogleCredentials(accessToken)
                            .createScoped(SCOPE)))
                    .build();
ejona86 commented 6 years ago

@mrthemuradin, doing so is all-advised and unsupported when communicating with Google. It breaks certain requirements of HTTP/2. If you maintain your own servers or if the service owner explicitly permits you, then you can change those settings.

While it is possible to use the older ciphers today, this is a hold-over on non-Android from Java 7's poor cipher performance. We handled this by discouraging use of Jetty ALPN in favor of tcnative. Since that problem has been solved for a while now (giving users time to migrate), we may enable the enforcement mandated by the HTTP/2 spec. That will break you.