25
25
import io .grpc .okhttp .internal .Protocol ;
26
26
import io .grpc .okhttp .internal .Util ;
27
27
import java .io .IOException ;
28
+ import java .lang .reflect .Constructor ;
29
+ import java .lang .reflect .InvocationTargetException ;
30
+ import java .lang .reflect .Method ;
28
31
import java .net .Socket ;
32
+ import java .util .ArrayList ;
33
+ import java .util .Arrays ;
34
+ import java .util .Collections ;
29
35
import java .util .List ;
30
36
import java .util .logging .Level ;
31
37
import java .util .logging .Logger ;
32
38
import javax .annotation .Nullable ;
39
+ import javax .net .ssl .SSLParameters ;
33
40
import javax .net .ssl .SSLSocket ;
34
41
35
42
/**
@@ -133,6 +140,69 @@ static final class AndroidNegotiator extends OkHttpProtocolNegotiator {
133
140
private static final OptionalMethod <Socket > SET_NPN_PROTOCOLS =
134
141
new OptionalMethod <>(null , "setNpnProtocols" , byte [].class );
135
142
143
+ // Non-null on Android 10.0+.
144
+ // SSLSockets.isSupportedSocket(SSLSocket)
145
+ private static final Method SSL_SOCKETS_IS_SUPPORTED_SOCKET ;
146
+ // SSLSockets.setUseSessionTickets(SSLSocket, boolean)
147
+ private static final Method SSL_SOCKETS_SET_USE_SESSION_TICKET ;
148
+ // SSLParameters.setApplicationProtocols(String[])
149
+ private static final Method SET_APPLICATION_PROTOCOLS ;
150
+ // SSLParameters.getApplicationProtocols()
151
+ private static final Method GET_APPLICATION_PROTOCOLS ;
152
+ // SSLSocket.getApplicationProtocol()
153
+ private static final Method GET_APPLICATION_PROTOCOL ;
154
+
155
+ // Non-null on Android 7.0+.
156
+ // SSLParameters.setServerNames(List<SNIServerName>)
157
+ private static final Method SET_SERVER_NAMES ;
158
+ // SNIHostName(String)
159
+ private static final Constructor <?> SNI_HOST_NAME ;
160
+
161
+ static {
162
+ // Attempt to find Android 10.0+ APIs.
163
+ Method setApplicationProtocolsMethod = null ;
164
+ Method getApplicationProtocolsMethod = null ;
165
+ Method getApplicationProtocolMethod = null ;
166
+ Method sslSocketsIsSupportedSocketMethod = null ;
167
+ Method sslSocketsSetUseSessionTicketsMethod = null ;
168
+ try {
169
+ Class <?> sslParameters = SSLParameters .class ;
170
+ setApplicationProtocolsMethod =
171
+ sslParameters .getMethod ("setApplicationProtocols" , String [].class );
172
+ getApplicationProtocolsMethod = sslParameters .getMethod ("getApplicationProtocols" );
173
+ getApplicationProtocolMethod = SSLSocket .class .getMethod ("getApplicationProtocol" );
174
+ Class <?> sslSockets = Class .forName ("android.net.ssl.SSLSockets" );
175
+ sslSocketsIsSupportedSocketMethod =
176
+ sslSockets .getMethod ("isSupportedSocket" , SSLSocket .class );
177
+ sslSocketsSetUseSessionTicketsMethod =
178
+ sslSockets .getMethod ("setUseSessionTickets" , SSLSocket .class , boolean .class );
179
+ } catch (ClassNotFoundException e ) {
180
+ logger .log (Level .FINER , "Failed to find Android 10.0+ APIs" , e );
181
+ } catch (NoSuchMethodException e ) {
182
+ logger .log (Level .FINER , "Failed to find Android 10.0+ APIs" , e );
183
+ }
184
+ SET_APPLICATION_PROTOCOLS = setApplicationProtocolsMethod ;
185
+ GET_APPLICATION_PROTOCOLS = getApplicationProtocolsMethod ;
186
+ GET_APPLICATION_PROTOCOL = getApplicationProtocolMethod ;
187
+ SSL_SOCKETS_IS_SUPPORTED_SOCKET = sslSocketsIsSupportedSocketMethod ;
188
+ SSL_SOCKETS_SET_USE_SESSION_TICKET = sslSocketsSetUseSessionTicketsMethod ;
189
+
190
+ // Attempt to find Android 7.0+ APIs.
191
+ Method setServerNamesMethod = null ;
192
+ Constructor <?> sniHostNameConstructor = null ;
193
+ try {
194
+ setServerNamesMethod = SSLParameters .class .getMethod ("setServerNames" , List .class );
195
+ sniHostNameConstructor =
196
+ Class .forName ("javax.net.ssl.SNIHostName" ).getConstructor (String .class );
197
+ } catch (ClassNotFoundException e ) {
198
+ logger .log (Level .FINER , "Failed to find Android 7.0+ APIs" , e );
199
+ } catch (NoSuchMethodException e ) {
200
+ logger .log (Level .FINER , "Failed to find Android 7.0+ APIs" , e );
201
+ }
202
+ SET_SERVER_NAMES = setServerNamesMethod ;
203
+ SNI_HOST_NAME = sniHostNameConstructor ;
204
+ }
205
+
136
206
AndroidNegotiator (Platform platform ) {
137
207
super (platform );
138
208
}
@@ -152,21 +222,75 @@ public String negotiate(SSLSocket sslSocket, String hostname, List<Protocol> pro
152
222
/**
153
223
* Override {@link Platform}'s configureTlsExtensions for Android older than 5.0, since OkHttp
154
224
* (2.3+) only support such function for Android 5.0+.
225
+ *
226
+ * <p>Note: Prior to Android Q, the standard way of accessing some Conscrypt features was to
227
+ * use reflection to call hidden APIs. Beginning in Q, there is public API for all of these
228
+ * features. We attempt to use the public API where possible. Otherwise, fall back to use the
229
+ * old reflective API.
155
230
*/
156
231
@ Override
157
232
protected void configureTlsExtensions (
158
233
SSLSocket sslSocket , String hostname , List <Protocol > protocols ) {
159
- // Enable SNI and session tickets.
160
- if (hostname != null ) {
161
- SET_USE_SESSION_TICKETS .invokeOptionalWithoutCheckedException (sslSocket , true );
162
- SET_HOSTNAME .invokeOptionalWithoutCheckedException (sslSocket , hostname );
234
+ String [] protocolNames = protocolIds (protocols );
235
+ SSLParameters sslParams = sslSocket .getSSLParameters ();
236
+ try {
237
+ // Enable SNI and session tickets.
238
+ if (hostname != null ) {
239
+ if (SSL_SOCKETS_IS_SUPPORTED_SOCKET != null
240
+ && (boolean ) SSL_SOCKETS_IS_SUPPORTED_SOCKET .invoke (null , sslSocket )) {
241
+ SSL_SOCKETS_SET_USE_SESSION_TICKET .invoke (null , sslSocket , true );
242
+ } else {
243
+ SET_USE_SESSION_TICKETS .invokeOptionalWithoutCheckedException (sslSocket , true );
244
+ }
245
+ if (SET_SERVER_NAMES != null && SNI_HOST_NAME != null ) {
246
+ SET_SERVER_NAMES
247
+ .invoke (sslParams , Collections .singletonList (SNI_HOST_NAME .newInstance (hostname )));
248
+ } else {
249
+ SET_HOSTNAME .invokeOptionalWithoutCheckedException (sslSocket , hostname );
250
+ }
251
+ }
252
+ boolean alpnEnabled = false ;
253
+ if (GET_APPLICATION_PROTOCOL != null ) {
254
+ try {
255
+ // If calling SSLSocket.getApplicationProtocol() throws UnsupportedOperationException,
256
+ // the underlying provider does not implement operations for enabling
257
+ // ALPN in the fashion of SSLParameters.setApplicationProtocols(). Fall back to
258
+ // use old hidden methods.
259
+ GET_APPLICATION_PROTOCOL .invoke (sslSocket );
260
+ SET_APPLICATION_PROTOCOLS .invoke (sslParams , (Object ) protocolNames );
261
+ alpnEnabled = true ;
262
+ } catch (InvocationTargetException e ) {
263
+ Throwable targetException = e .getTargetException ();
264
+ if (targetException instanceof UnsupportedOperationException ) {
265
+ logger .log (Level .FINER , "setApplicationProtocol unsupported, will try old methods" );
266
+ } else {
267
+ throw e ;
268
+ }
269
+ }
270
+ }
271
+ sslSocket .setSSLParameters (sslParams );
272
+ // Check application protocols are configured correctly. If not, configure again with
273
+ // old methods.
274
+ // Workaround for Conscrypt bug: https://github.com/google/conscrypt/issues/832
275
+ if (alpnEnabled && GET_APPLICATION_PROTOCOLS != null ) {
276
+ String [] configuredProtocols =
277
+ (String []) GET_APPLICATION_PROTOCOLS .invoke (sslSocket .getSSLParameters ());
278
+ if (Arrays .equals (protocolNames , configuredProtocols )) {
279
+ return ;
280
+ }
281
+ }
282
+ } catch (IllegalAccessException e ) {
283
+ throw new RuntimeException (e );
284
+ } catch (InvocationTargetException e ) {
285
+ throw new RuntimeException (e );
286
+ } catch (InstantiationException e ) {
287
+ throw new RuntimeException (e );
163
288
}
164
289
165
290
Object [] parameters = {Platform .concatLengthPrefixed (protocols )};
166
291
if (platform .getTlsExtensionType () == TlsExtensionType .ALPN_AND_NPN ) {
167
292
SET_ALPN_PROTOCOLS .invokeWithoutCheckedException (sslSocket , parameters );
168
293
}
169
-
170
294
if (platform .getTlsExtensionType () != TlsExtensionType .NONE ) {
171
295
SET_NPN_PROTOCOLS .invokeWithoutCheckedException (sslSocket , parameters );
172
296
} else {
@@ -177,6 +301,23 @@ protected void configureTlsExtensions(
177
301
178
302
@ Override
179
303
public String getSelectedProtocol (SSLSocket socket ) {
304
+ if (GET_APPLICATION_PROTOCOL != null ) {
305
+ try {
306
+ return (String ) GET_APPLICATION_PROTOCOL .invoke (socket );
307
+ } catch (IllegalAccessException e ) {
308
+ throw new RuntimeException (e );
309
+ } catch (InvocationTargetException e ) {
310
+ Throwable targetException = e .getTargetException ();
311
+ if (targetException instanceof UnsupportedOperationException ) {
312
+ logger .log (
313
+ Level .FINER ,
314
+ "Socket unsupported for getApplicationProtocol, will try old methods" );
315
+ } else {
316
+ throw new RuntimeException (e );
317
+ }
318
+ }
319
+ }
320
+
180
321
if (platform .getTlsExtensionType () == TlsExtensionType .ALPN_AND_NPN ) {
181
322
try {
182
323
byte [] alpnResult =
@@ -207,4 +348,12 @@ public String getSelectedProtocol(SSLSocket socket) {
207
348
return null ;
208
349
}
209
350
}
351
+
352
+ private static String [] protocolIds (List <Protocol > protocols ) {
353
+ List <String > result = new ArrayList <>();
354
+ for (Protocol protocol : protocols ) {
355
+ result .add (protocol .toString ());
356
+ }
357
+ return result .toArray (new String [0 ]);
358
+ }
210
359
}
0 commit comments