• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20258

01 May 2026 01:55PM UTC coverage: 88.797% (-0.03%) from 88.827%
#20258

push

github

web-flow
okhttp: enable TLS 1.3 on Android, retain TLS 1.2-only for desktop JVM

The ConnectionSpec used by OkHttpChannelBuilder had TLS 1.3 explicitly
disabled since Dec 2020 due to a Conscrypt/SunJSSE incompatibility.
However, this incompatibility does not affect Android. The previous code
applied the TLS 1.2-only restriction unconditionally to all platforms.

Regulatory impact: TLS 1.2 is classified as a legacy mechanism in ENISA
Agreed Cryptographic Mechanisms v2.0 (April 2025), with TLS 1.3 listed
as the recommended protocol. This limitation has been forcing all
downstream components using grpc-okhttp on Android to operate with a
legacy protocol, creating compliance friction with the EU Radio
Equipment Directive (RED) and EU Cyber Resilience Act (CRA)
certification requirements.

Fixes: https://github.com/grpc/grpc-java/issues/7431 (Android only)
Fixes: https://github.com/grpc/grpc-java/issues/7765 (Android only)

36136 of 40695 relevant lines covered (88.8%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.27
/../okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java
1
/*
2
 * Copyright 2014 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.okhttp;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20
import static io.grpc.internal.CertificateUtils.createTrustManager;
21
import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
22
import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED;
23

24
import com.google.common.annotations.VisibleForTesting;
25
import com.google.common.base.Preconditions;
26
import com.google.errorprone.annotations.CheckReturnValue;
27
import io.grpc.CallCredentials;
28
import io.grpc.ChannelCredentials;
29
import io.grpc.ChannelLogger;
30
import io.grpc.ChoiceChannelCredentials;
31
import io.grpc.CompositeCallCredentials;
32
import io.grpc.CompositeChannelCredentials;
33
import io.grpc.ExperimentalApi;
34
import io.grpc.ForwardingChannelBuilder2;
35
import io.grpc.InsecureChannelCredentials;
36
import io.grpc.Internal;
37
import io.grpc.ManagedChannelBuilder;
38
import io.grpc.TlsChannelCredentials;
39
import io.grpc.internal.AtomicBackoff;
40
import io.grpc.internal.ClientTransportFactory;
41
import io.grpc.internal.ConnectionClientTransport;
42
import io.grpc.internal.FixedObjectPool;
43
import io.grpc.internal.GrpcUtil;
44
import io.grpc.internal.KeepAliveManager;
45
import io.grpc.internal.ManagedChannelImplBuilder;
46
import io.grpc.internal.ManagedChannelImplBuilder.ChannelBuilderDefaultPortProvider;
47
import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder;
48
import io.grpc.internal.ObjectPool;
49
import io.grpc.internal.SharedResourceHolder.Resource;
50
import io.grpc.internal.SharedResourcePool;
51
import io.grpc.internal.TransportTracer;
52
import io.grpc.okhttp.internal.CipherSuite;
53
import io.grpc.okhttp.internal.ConnectionSpec;
54
import io.grpc.okhttp.internal.Platform;
55
import io.grpc.okhttp.internal.TlsVersion;
56
import io.grpc.util.CertificateUtils;
57
import java.io.ByteArrayInputStream;
58
import java.io.IOException;
59
import java.io.InputStream;
60
import java.net.InetSocketAddress;
61
import java.net.SocketAddress;
62
import java.security.GeneralSecurityException;
63
import java.security.KeyStore;
64
import java.security.PrivateKey;
65
import java.security.cert.X509Certificate;
66
import java.util.Collection;
67
import java.util.Collections;
68
import java.util.EnumSet;
69
import java.util.Set;
70
import java.util.concurrent.Executor;
71
import java.util.concurrent.ExecutorService;
72
import java.util.concurrent.Executors;
73
import java.util.concurrent.ScheduledExecutorService;
74
import java.util.concurrent.TimeUnit;
75
import java.util.logging.Level;
76
import java.util.logging.Logger;
77
import javax.annotation.Nullable;
78
import javax.net.SocketFactory;
79
import javax.net.ssl.HostnameVerifier;
80
import javax.net.ssl.KeyManager;
81
import javax.net.ssl.KeyManagerFactory;
82
import javax.net.ssl.SSLContext;
83
import javax.net.ssl.SSLSocketFactory;
84
import javax.net.ssl.TrustManager;
85

86
/** Convenience class for building channels with the OkHttp transport. */
87
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1785")
88
public final class OkHttpChannelBuilder extends ForwardingChannelBuilder2<OkHttpChannelBuilder> {
89
  private static final Logger log = Logger.getLogger(OkHttpChannelBuilder.class.getName());
1✔
90
  public static final int DEFAULT_FLOW_CONTROL_WINDOW = 65535;
91

92
  private final ManagedChannelImplBuilder managedChannelImplBuilder;
93
  private final ChannelCredentials channelCredentials;
94
  private TransportTracer.Factory transportTracerFactory = TransportTracer.getDefaultFactory();
1✔
95

96

97
  /** Identifies the negotiation used for starting up HTTP/2. */
98
  private enum NegotiationType {
1✔
99
    /** Uses TLS ALPN/NPN negotiation, assumes an SSL connection. */
100
    TLS,
1✔
101

102
    /**
103
     * Just assume the connection is plaintext (non-SSL) and the remote endpoint supports HTTP/2
104
     * directly without an upgrade.
105
     */
106
    PLAINTEXT
1✔
107
  }
108

109
  // @VisibleForTesting
110
  static final ConnectionSpec INTERNAL_DEFAULT_CONNECTION_SPEC =
1✔
111
      new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
112
          .cipherSuites(
1✔
113
              // The following items should be sync with Netty's Http2SecurityUtil.CIPHERS.
114
              CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
115
              CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
116
              CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
117
              CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
118
              CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
119
              CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
120
              CipherSuite.TLS_AES_128_GCM_SHA256,
121
              CipherSuite.TLS_AES_256_GCM_SHA384,
122
              CipherSuite.TLS_CHACHA20_POLY1305_SHA256)
123
          .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
1✔
124
          .supportsTlsExtensions(true)
1✔
125
          .build();
1✔
126

127
  // @VisibleForTesting
128
  static final ConnectionSpec INTERNAL_LEGACY_CONNECTION_SPEC =
1✔
129
      new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
130
          .cipherSuites(
1✔
131
              // The following items should be sync with Netty's Http2SecurityUtil.CIPHERS.
132
              CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
133
              CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
134
              CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
135
              CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
136
              CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
137
              CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)
138
          .tlsVersions(TlsVersion.TLS_1_2)
1✔
139
          .supportsTlsExtensions(true)
1✔
140
          .build();
1✔
141

142
  private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L);
1✔
143
  private static final Resource<Executor> SHARED_EXECUTOR =
1✔
144
      new Resource<Executor>() {
1✔
145
        @Override
146
        public Executor create() {
147
          return Executors.newCachedThreadPool(GrpcUtil.getThreadFactory("grpc-okhttp-%d", true));
1✔
148
        }
149

150
        @Override
151
        public void close(Executor executor) {
152
          ((ExecutorService) executor).shutdown();
1✔
153
        }
1✔
154
      };
155
  static final ObjectPool<Executor> DEFAULT_TRANSPORT_EXECUTOR_POOL =
1✔
156
      SharedResourcePool.forResource(SHARED_EXECUTOR);
1✔
157

158
  /** Creates a new builder for the given server host and port. */
159
  public static OkHttpChannelBuilder forAddress(String host, int port) {
160
    return new OkHttpChannelBuilder(host, port);
1✔
161
  }
162

163
  /** Creates a new builder with the given host and port. */
164
  public static OkHttpChannelBuilder forAddress(String host, int port, ChannelCredentials creds) {
165
    return forTarget(GrpcUtil.authorityFromHostAndPort(host, port), creds);
1✔
166
  }
167

168
  /**
169
   * Creates a new builder for the given target that will be resolved by
170
   * {@link io.grpc.NameResolver}.
171
   */
172
  public static OkHttpChannelBuilder forTarget(String target) {
173
    return new OkHttpChannelBuilder(target);
1✔
174
  }
175

176
  /**
177
   * Creates a new builder for the given target that will be resolved by
178
   * {@link io.grpc.NameResolver}.
179
   */
180
  public static OkHttpChannelBuilder forTarget(String target, ChannelCredentials creds) {
181
    SslSocketFactoryResult result = sslSocketFactoryFrom(creds);
1✔
182
    if (result.error != null) {
1✔
183
      throw new IllegalArgumentException(result.error);
×
184
    }
185
    return new OkHttpChannelBuilder(target, creds, result.callCredentials, result.factory);
1✔
186
  }
187

188
  private ObjectPool<Executor> transportExecutorPool = DEFAULT_TRANSPORT_EXECUTOR_POOL;
1✔
189
  private ObjectPool<ScheduledExecutorService> scheduledExecutorServicePool =
1✔
190
      SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE);
1✔
191

192
  private SocketFactory socketFactory;
193
  private SSLSocketFactory sslSocketFactory;
194
  private final boolean freezeSecurityConfiguration;
195
  private HostnameVerifier hostnameVerifier;
196
  private ConnectionSpec connectionSpec = initialConnectionSpec();
1✔
197
  private NegotiationType negotiationType = NegotiationType.TLS;
1✔
198
  private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
1✔
199
  private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
1✔
200
  private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW;
1✔
201
  private boolean keepAliveWithoutCalls;
202
  private int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
1✔
203
  private int maxInboundMetadataSize = Integer.MAX_VALUE;
1✔
204

205
  /**
206
   * If true, indicates that the transport may use the GET method for RPCs, and may include the
207
   * request body in the query params.
208
   */
209
  private final boolean useGetForSafeMethods = false;
1✔
210

211
  private static ConnectionSpec initialConnectionSpec() {
212
    return (OkHttpProtocolNegotiator.get() instanceof OkHttpProtocolNegotiator.AndroidNegotiator)
1✔
213
        ? INTERNAL_DEFAULT_CONNECTION_SPEC
×
214
        : INTERNAL_LEGACY_CONNECTION_SPEC;
1✔
215
  }
216

217
  private OkHttpChannelBuilder(String host, int port) {
218
    this(GrpcUtil.authorityFromHostAndPort(host, port));
1✔
219
  }
1✔
220

221
  private OkHttpChannelBuilder(String target) {
1✔
222
    managedChannelImplBuilder = new ManagedChannelImplBuilder(target,
1✔
223
        new OkHttpChannelTransportFactoryBuilder(),
224
        new OkHttpChannelDefaultPortProvider());
225
    this.freezeSecurityConfiguration = false;
1✔
226
    this.channelCredentials = null;
1✔
227
  }
1✔
228

229
  OkHttpChannelBuilder(
230
      String target, ChannelCredentials channelCreds, CallCredentials callCreds,
231
      SSLSocketFactory factory) {
1✔
232
    managedChannelImplBuilder = new ManagedChannelImplBuilder(
1✔
233
        target, channelCreds, callCreds,
234
        new OkHttpChannelTransportFactoryBuilder(),
235
        new OkHttpChannelDefaultPortProvider());
236
    this.sslSocketFactory = factory;
1✔
237
    this.negotiationType = factory == null ? NegotiationType.PLAINTEXT : NegotiationType.TLS;
1✔
238
    this.freezeSecurityConfiguration = true;
1✔
239
    this.channelCredentials = channelCreds;
1✔
240
  }
1✔
241

242
  private final class OkHttpChannelTransportFactoryBuilder
1✔
243
      implements ClientTransportFactoryBuilder {
244
    @Override
245
    public ClientTransportFactory buildClientTransportFactory() {
246
      return buildTransportFactory();
1✔
247
    }
248
  }
249

250
  private final class OkHttpChannelDefaultPortProvider
1✔
251
      implements ChannelBuilderDefaultPortProvider {
252
    @Override
253
    public int getDefaultPort() {
254
      return OkHttpChannelBuilder.this.getDefaultPort();
1✔
255
    }
256
  }
257

258
  @Internal
259
  @Override
260
  protected ManagedChannelBuilder<?> delegate() {
261
    return managedChannelImplBuilder;
1✔
262
  }
263

264
  @VisibleForTesting
265
  OkHttpChannelBuilder setTransportTracerFactory(TransportTracer.Factory transportTracerFactory) {
266
    this.transportTracerFactory = transportTracerFactory;
1✔
267
    return this;
1✔
268
  }
269

270
  /**
271
   * Override the default executor necessary for internal transport use.
272
   *
273
   * <p>The channel does not take ownership of the given executor. It is the caller' responsibility
274
   * to shutdown the executor when appropriate.
275
   */
276
  public OkHttpChannelBuilder transportExecutor(@Nullable Executor transportExecutor) {
277
    if (transportExecutor == null) {
1✔
278
      this.transportExecutorPool = DEFAULT_TRANSPORT_EXECUTOR_POOL;
×
279
    } else {
280
      this.transportExecutorPool = new FixedObjectPool<>(transportExecutor);
1✔
281
    }
282
    return this;
1✔
283
  }
284

285
  /**
286
   * Override the default {@link SocketFactory} used to create sockets. If the socket factory is not
287
   * set or set to null, a default one will be used.
288
   *
289
   * @since 1.20.0
290
   */
291
  public OkHttpChannelBuilder socketFactory(@Nullable SocketFactory socketFactory) {
292
    this.socketFactory = socketFactory;
1✔
293
    return this;
1✔
294
  }
295

296
  /**
297
   * Sets the negotiation type for the HTTP/2 connection.
298
   *
299
   * <p>If TLS is enabled a default {@link SSLSocketFactory} is created using the best
300
   * {@link java.security.Provider} available and is NOT based on
301
   * {@link SSLSocketFactory#getDefault}. To more precisely control the TLS configuration call
302
   * {@link #sslSocketFactory} to override the socket factory used.
303
   *
304
   * <p>Default: <code>TLS</code>
305
   *
306
   * @deprecated use {@link #usePlaintext()} or {@link #useTransportSecurity()} instead.
307
   */
308
  @Deprecated
309
  public OkHttpChannelBuilder negotiationType(io.grpc.okhttp.NegotiationType type) {
310
    Preconditions.checkState(!freezeSecurityConfiguration,
×
311
        "Cannot change security when using ChannelCredentials");
312
    Preconditions.checkNotNull(type, "type");
×
313
    switch (type) {
×
314
      case TLS:
315
        negotiationType = NegotiationType.TLS;
×
316
        break;
×
317
      case PLAINTEXT:
318
        negotiationType = NegotiationType.PLAINTEXT;
×
319
        break;
×
320
      default:
321
        throw new AssertionError("Unknown negotiation type: " + type);
×
322
    }
323
    return this;
×
324
  }
325

326
  /**
327
   * {@inheritDoc}
328
   *
329
   * @since 1.3.0
330
   */
331
  @Override
332
  public OkHttpChannelBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
333
    Preconditions.checkArgument(keepAliveTime > 0L, "keepalive time must be positive");
×
334
    keepAliveTimeNanos = timeUnit.toNanos(keepAliveTime);
×
335
    keepAliveTimeNanos = KeepAliveManager.clampKeepAliveTimeInNanos(keepAliveTimeNanos);
×
336
    if (keepAliveTimeNanos >= AS_LARGE_AS_INFINITE) {
×
337
      // Bump keepalive time to infinite. This disables keepalive.
338
      keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
×
339
    }
340
    return this;
×
341
  }
342

343
  /**
344
   * {@inheritDoc}
345
   *
346
   * @since 1.3.0
347
   */
348
  @Override
349
  public OkHttpChannelBuilder keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
350
    Preconditions.checkArgument(keepAliveTimeout > 0L, "keepalive timeout must be positive");
×
351
    keepAliveTimeoutNanos = timeUnit.toNanos(keepAliveTimeout);
×
352
    keepAliveTimeoutNanos = KeepAliveManager.clampKeepAliveTimeoutInNanos(keepAliveTimeoutNanos);
×
353
    return this;
×
354
  }
355

356
  /**
357
   * Sets the flow control window in bytes. If not called, the default value
358
   * is {@link #DEFAULT_FLOW_CONTROL_WINDOW}).
359
   */
360
  public OkHttpChannelBuilder flowControlWindow(int flowControlWindow) {
361
    Preconditions.checkState(flowControlWindow > 0, "flowControlWindow must be positive");
1✔
362
    this.flowControlWindow = flowControlWindow;
1✔
363
    return this;
1✔
364
  }
365

366
  /**
367
   * {@inheritDoc}
368
   *
369
   * @since 1.3.0
370
   * @see #keepAliveTime(long, TimeUnit)
371
   */
372
  @Override
373
  public OkHttpChannelBuilder keepAliveWithoutCalls(boolean enable) {
374
    keepAliveWithoutCalls = enable;
×
375
    return this;
×
376
  }
377

378
  /**
379
   * Override the default {@link SSLSocketFactory} and enable TLS negotiation.
380
   */
381
  public OkHttpChannelBuilder sslSocketFactory(SSLSocketFactory factory) {
382
    Preconditions.checkState(!freezeSecurityConfiguration,
1✔
383
        "Cannot change security when using ChannelCredentials");
384
    this.sslSocketFactory = factory;
1✔
385
    negotiationType = NegotiationType.TLS;
1✔
386
    return this;
1✔
387
  }
388

389
  /**
390
   * Set the hostname verifier to use when using TLS negotiation. The hostnameVerifier is only used
391
   * if using TLS negotiation. If the hostname verifier is not set, a default hostname verifier is
392
   * used.
393
   *
394
   * <p>Be careful when setting a custom hostname verifier! By setting a non-null value, you are
395
   * replacing all default verification behavior. If the hostname verifier you supply does not
396
   * effectively supply the same checks, you may be removing the security assurances that TLS aims
397
   * to provide.</p>
398
   *
399
   * <p>This method should not be used to avoid hostname verification, even during testing, since
400
   * {@link #overrideAuthority} is a safer alternative as it does not disable any security checks.
401
   * </p>
402
   *
403
   * @see io.grpc.okhttp.internal.OkHostnameVerifier
404
   *
405
   * @since 1.6.0
406
   * @return this
407
   *
408
   */
409
  public OkHttpChannelBuilder hostnameVerifier(@Nullable HostnameVerifier hostnameVerifier) {
410
    Preconditions.checkState(!freezeSecurityConfiguration,
1✔
411
        "Cannot change security when using ChannelCredentials");
412
    this.hostnameVerifier = hostnameVerifier;
1✔
413
    return this;
1✔
414
  }
415

416
  /**
417
   * For secure connection, provides a ConnectionSpec to specify Cipher suite and
418
   * TLS versions.
419
   *
420
   * <p>By default a modern, HTTP/2-compatible spec will be used.
421
   *
422
   * <p>This method is only used when building a secure connection. For plaintext
423
   * connection, use {@link #usePlaintext()} instead.
424
   *
425
   * @throws IllegalArgumentException
426
   *         If {@code connectionSpec} is not with TLS
427
   */
428
  public OkHttpChannelBuilder connectionSpec(
429
      com.squareup.okhttp.ConnectionSpec connectionSpec) {
430
    Preconditions.checkState(!freezeSecurityConfiguration,
1✔
431
        "Cannot change security when using ChannelCredentials");
432
    Preconditions.checkArgument(connectionSpec.isTls(), "plaintext ConnectionSpec is not accepted");
1✔
433
    this.connectionSpec = Utils.convertSpec(connectionSpec);
1✔
434
    return this;
1✔
435
  }
436

437
  /**
438
   * Sets the connection specification used for secure connections.
439
   *
440
   * <p>By default a modern, HTTP/2-compatible spec will be used.
441
   *
442
   * <p>This method is only used when building a secure connection. For plaintext
443
   * connection, use {@link #usePlaintext()} instead.
444
   *
445
   * @param tlsVersions List of tls versions.
446
   * @param cipherSuites List of cipher suites.
447
   *
448
   * @since  1.43.0
449
   */
450
  public OkHttpChannelBuilder tlsConnectionSpec(
451
          String[] tlsVersions, String[] cipherSuites) {
452
    Preconditions.checkState(!freezeSecurityConfiguration,
×
453
            "Cannot change security when using ChannelCredentials");
454
    Preconditions.checkNotNull(tlsVersions, "tls versions must not null");
×
455
    Preconditions.checkNotNull(cipherSuites, "ciphers must not null");
×
456

457
    this.connectionSpec = new ConnectionSpec.Builder(true)
×
458
            .supportsTlsExtensions(true)
×
459
            .tlsVersions(tlsVersions)
×
460
            .cipherSuites(cipherSuites)
×
461
            .build();
×
462
    return this;
×
463
  }
464

465
  /** Sets the negotiation type for the HTTP/2 connection to plaintext. */
466
  @Override
467
  public OkHttpChannelBuilder usePlaintext() {
468
    Preconditions.checkState(!freezeSecurityConfiguration,
1✔
469
        "Cannot change security when using ChannelCredentials");
470
    negotiationType = NegotiationType.PLAINTEXT;
1✔
471
    return this;
1✔
472
  }
473

474
  /**
475
   * Sets the negotiation type for the HTTP/2 connection to TLS (this is the default).
476
   *
477
   * <p>With TLS enabled, a default {@link SSLSocketFactory} is created using the best {@link
478
   * java.security.Provider} available and is NOT based on {@link SSLSocketFactory#getDefault}. To
479
   * more precisely control the TLS configuration call {@link #sslSocketFactory(SSLSocketFactory)}
480
   * to override the socket factory used.
481
   */
482
  @Override
483
  public OkHttpChannelBuilder useTransportSecurity() {
484
    Preconditions.checkState(!freezeSecurityConfiguration,
×
485
        "Cannot change security when using ChannelCredentials");
486
    negotiationType = NegotiationType.TLS;
×
487
    return this;
×
488
  }
489

490
  /**
491
   * Provides a custom scheduled executor service.
492
   *
493
   * <p>It's an optional parameter. If the user has not provided a scheduled executor service when
494
   * the channel is built, the builder will use a static cached thread pool.
495
   *
496
   * @return this
497
   *
498
   * @since 1.11.0
499
   */
500
  public OkHttpChannelBuilder scheduledExecutorService(
501
      ScheduledExecutorService scheduledExecutorService) {
502
    this.scheduledExecutorServicePool =
1✔
503
        new FixedObjectPool<>(checkNotNull(scheduledExecutorService, "scheduledExecutorService"));
1✔
504
    return this;
1✔
505
  }
506

507
  /**
508
   * Sets the maximum size of metadata allowed to be received. {@code Integer.MAX_VALUE} disables
509
   * the enforcement. Defaults to no limit ({@code Integer.MAX_VALUE}).
510
   *
511
   * <p>The implementation does not currently limit memory usage; this value is checked only after
512
   * the metadata is decoded from the wire. It does prevent large metadata from being passed to the
513
   * application.
514
   *
515
   * @param bytes the maximum size of received metadata
516
   * @return this
517
   * @throws IllegalArgumentException if bytes is non-positive
518
   * @since 1.17.0
519
   */
520
  @Override
521
  public OkHttpChannelBuilder maxInboundMetadataSize(int bytes) {
522
    Preconditions.checkArgument(bytes > 0, "maxInboundMetadataSize must be > 0");
1✔
523
    this.maxInboundMetadataSize = bytes;
1✔
524
    return this;
1✔
525
  }
526

527
  /**
528
   * Sets the maximum message size allowed for a single gRPC frame. If an inbound messages
529
   * larger than this limit is received it will not be processed and the RPC will fail with
530
   * RESOURCE_EXHAUSTED.
531
   */
532
  @Override
533
  public OkHttpChannelBuilder maxInboundMessageSize(int max) {
534
    Preconditions.checkArgument(max >= 0, "negative max");
1✔
535
    maxInboundMessageSize = max;
1✔
536
    return this;
1✔
537
  }
538

539
  OkHttpTransportFactory buildTransportFactory() {
540
    boolean enableKeepAlive = keepAliveTimeNanos != KEEPALIVE_TIME_NANOS_DISABLED;
1✔
541
    return new OkHttpTransportFactory(
1✔
542
        transportExecutorPool,
543
        scheduledExecutorServicePool,
544
        socketFactory,
545
        createSslSocketFactory(),
1✔
546
        hostnameVerifier,
547
        connectionSpec,
548
        maxInboundMessageSize,
549
        enableKeepAlive,
550
        keepAliveTimeNanos,
551
        keepAliveTimeoutNanos,
552
        flowControlWindow,
553
        keepAliveWithoutCalls,
554
        maxInboundMetadataSize,
555
        transportTracerFactory,
556
        useGetForSafeMethods,
557
        channelCredentials);
558
  }
559

560
  OkHttpChannelBuilder disableCheckAuthority() {
561
    this.managedChannelImplBuilder.disableCheckAuthority();
1✔
562
    return this;
1✔
563
  }
564

565
  OkHttpChannelBuilder enableCheckAuthority() {
566
    this.managedChannelImplBuilder.enableCheckAuthority();
1✔
567
    return this;
1✔
568
  }
569

570
  int getDefaultPort() {
571
    switch (negotiationType) {
1✔
572
      case PLAINTEXT:
573
        return GrpcUtil.DEFAULT_PORT_PLAINTEXT;
1✔
574
      case TLS:
575
        return GrpcUtil.DEFAULT_PORT_SSL;
1✔
576
      default:
577
        throw new AssertionError(negotiationType + " not handled");
×
578
    }
579
  }
580

581
  void setStatsEnabled(boolean value) {
582
    this.managedChannelImplBuilder.setStatsEnabled(value);
1✔
583
  }
1✔
584

585
  @VisibleForTesting
586
  @Nullable
587
  SSLSocketFactory createSslSocketFactory() {
588
    switch (negotiationType) {
1✔
589
      case TLS:
590
        try {
591
          if (sslSocketFactory == null) {
1✔
592
            SSLContext sslContext = SSLContext.getInstance("Default", Platform.get().getProvider());
1✔
593
            sslSocketFactory = sslContext.getSocketFactory();
1✔
594
          }
595
          return sslSocketFactory;
1✔
596
        } catch (GeneralSecurityException gse) {
×
597
          throw new RuntimeException("TLS Provider failure", gse);
×
598
        }
599
      case PLAINTEXT:
600
        return null;
1✔
601
      default:
602
        throw new RuntimeException("Unknown negotiation type: " + negotiationType);
×
603
    }
604
  }
605

606
  private static final EnumSet<TlsChannelCredentials.Feature> understoodTlsFeatures =
1✔
607
      EnumSet.of(
1✔
608
          TlsChannelCredentials.Feature.MTLS, TlsChannelCredentials.Feature.CUSTOM_MANAGERS);
609

610
  static SslSocketFactoryResult sslSocketFactoryFrom(ChannelCredentials creds) {
611
    if (creds instanceof TlsChannelCredentials) {
1✔
612
      TlsChannelCredentials tlsCreds = (TlsChannelCredentials) creds;
1✔
613
      Set<TlsChannelCredentials.Feature> incomprehensible =
1✔
614
          tlsCreds.incomprehensible(understoodTlsFeatures);
1✔
615
      if (!incomprehensible.isEmpty()) {
1✔
616
        return SslSocketFactoryResult.error(
1✔
617
            "TLS features not understood: " + incomprehensible);
618
      }
619
      KeyManager[] km = null;
1✔
620
      if (tlsCreds.getKeyManagers() != null) {
1✔
621
        km = tlsCreds.getKeyManagers().toArray(new KeyManager[0]);
1✔
622
      } else if (tlsCreds.getPrivateKey() != null) {
1✔
623
        if (tlsCreds.getPrivateKeyPassword() != null) {
1✔
624
          return SslSocketFactoryResult.error("byte[]-based private key with password unsupported. "
1✔
625
              + "Use unencrypted file or KeyManager");
626
        }
627
        try {
628
          km = createKeyManager(tlsCreds.getCertificateChain(), tlsCreds.getPrivateKey());
1✔
629
        } catch (GeneralSecurityException gse) {
×
630
          log.log(Level.FINE, "Exception loading private key from credential", gse);
×
631
          return SslSocketFactoryResult.error("Unable to load private key: " + gse.getMessage());
×
632
        }
1✔
633
      } // else don't have a client cert
634
      TrustManager[] tm = null;
1✔
635
      if (tlsCreds.getTrustManagers() != null) {
1✔
636
        tm = tlsCreds.getTrustManagers().toArray(new TrustManager[0]);
1✔
637
      } else if (tlsCreds.getRootCertificates() != null) {
1✔
638
        try {
639
          tm = createTrustManager(tlsCreds.getRootCertificates());
1✔
640
        } catch (GeneralSecurityException gse) {
×
641
          log.log(Level.FINE, "Exception loading root certificates from credential", gse);
×
642
          return SslSocketFactoryResult.error(
×
643
              "Unable to load root certificates: " + gse.getMessage());
×
644
        }
1✔
645
      } // else use system default
646
      SSLContext sslContext;
647
      try {
648
        sslContext = SSLContext.getInstance("TLS", Platform.get().getProvider());
1✔
649
        sslContext.init(km, tm, null);
1✔
650
      } catch (GeneralSecurityException gse) {
×
651
        throw new RuntimeException("TLS Provider failure", gse);
×
652
      }
1✔
653
      return SslSocketFactoryResult.factory(sslContext.getSocketFactory());
1✔
654

655
    } else if (creds instanceof InsecureChannelCredentials) {
1✔
656
      return SslSocketFactoryResult.plaintext();
1✔
657

658
    } else if (creds instanceof CompositeChannelCredentials) {
1✔
659
      CompositeChannelCredentials compCreds = (CompositeChannelCredentials) creds;
1✔
660
      return sslSocketFactoryFrom(compCreds.getChannelCredentials())
1✔
661
          .withCallCredentials(compCreds.getCallCredentials());
1✔
662

663
    } else if (creds instanceof SslSocketFactoryChannelCredentials.ChannelCredentials) {
1✔
664
      SslSocketFactoryChannelCredentials.ChannelCredentials factoryCreds =
1✔
665
          (SslSocketFactoryChannelCredentials.ChannelCredentials) creds;
666
      return SslSocketFactoryResult.factory(factoryCreds.getFactory());
1✔
667

668
    } else if (creds instanceof ChoiceChannelCredentials) {
1✔
669
      ChoiceChannelCredentials choiceCreds = (ChoiceChannelCredentials) creds;
1✔
670
      StringBuilder error = new StringBuilder();
1✔
671
      for (ChannelCredentials innerCreds : choiceCreds.getCredentialsList()) {
1✔
672
        SslSocketFactoryResult result = sslSocketFactoryFrom(innerCreds);
1✔
673
        if (result.error == null) {
1✔
674
          return result;
1✔
675
        }
676
        error.append(", ");
1✔
677
        error.append(result.error);
1✔
678
      }
1✔
679
      return SslSocketFactoryResult.error(error.substring(2));
1✔
680

681
    } else {
682
      return SslSocketFactoryResult.error(
1✔
683
          "Unsupported credential type: " + creds.getClass().getName());
1✔
684
    }
685
  }
686

687
  static KeyManager[] createKeyManager(byte[] certChain, byte[] privateKey)
688
      throws GeneralSecurityException {
689
    InputStream certChainStream = new ByteArrayInputStream(certChain);
1✔
690
    InputStream privateKeyStream = new ByteArrayInputStream(privateKey);
1✔
691
    try {
692
      return createKeyManager(certChainStream, privateKeyStream);
1✔
693
    } finally {
694
      GrpcUtil.closeQuietly(certChainStream);
1✔
695
      GrpcUtil.closeQuietly(privateKeyStream);
1✔
696
    }
697
  }
698

699
  static KeyManager[] createKeyManager(InputStream certChain, InputStream privateKey)
700
      throws GeneralSecurityException {
701
    X509Certificate[] chain = CertificateUtils.getX509Certificates(certChain);
1✔
702
    PrivateKey key;
703
    try {
704
      key = CertificateUtils.getPrivateKey(privateKey);
1✔
705
    } catch (IOException uee) {
×
706
      throw new GeneralSecurityException("Unable to decode private key", uee);
×
707
    }
1✔
708
    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
1✔
709
    try {
710
      ks.load(null, null);
1✔
711
    } catch (IOException ex) {
×
712
      // Shouldn't really happen, as we're not loading any data.
713
      throw new GeneralSecurityException(ex);
×
714
    }
1✔
715
    ks.setKeyEntry("key", key, new char[0], chain);
1✔
716

717
    KeyManagerFactory keyManagerFactory =
718
        KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
1✔
719
    keyManagerFactory.init(ks, new char[0]);
1✔
720
    return keyManagerFactory.getKeyManagers();
1✔
721
  }
722

723
  static TrustManager[] createTrustManager(byte[] rootCerts) throws GeneralSecurityException {
724
    InputStream rootCertsStream = new ByteArrayInputStream(rootCerts);
1✔
725
    try {
726
      return io.grpc.internal.CertificateUtils.createTrustManager(rootCertsStream);
1✔
727
    } finally {
728
      GrpcUtil.closeQuietly(rootCertsStream);
1✔
729
    }
730
  }
731

732
  static Collection<Class<? extends SocketAddress>> getSupportedSocketAddressTypes() {
733
    return Collections.singleton(InetSocketAddress.class);
1✔
734
  }
735

736
  static final class SslSocketFactoryResult {
737
    /** {@code null} implies plaintext if {@code error == null}. */
738
    public final SSLSocketFactory factory;
739
    public final CallCredentials callCredentials;
740
    public final String error;
741

742
    private SslSocketFactoryResult(SSLSocketFactory factory, CallCredentials creds, String error) {
1✔
743
      this.factory = factory;
1✔
744
      this.callCredentials = creds;
1✔
745
      this.error = error;
1✔
746
    }
1✔
747

748
    public static SslSocketFactoryResult error(String error) {
749
      return new SslSocketFactoryResult(
1✔
750
          null, null, Preconditions.checkNotNull(error, "error"));
1✔
751
    }
752

753
    public static SslSocketFactoryResult plaintext() {
754
      return new SslSocketFactoryResult(null, null, null);
1✔
755
    }
756

757
    public static SslSocketFactoryResult factory(
758
        SSLSocketFactory factory) {
759
      return new SslSocketFactoryResult(
1✔
760
          Preconditions.checkNotNull(factory, "factory"), null, null);
1✔
761
    }
762

763
    public SslSocketFactoryResult withCallCredentials(CallCredentials callCreds) {
764
      Preconditions.checkNotNull(callCreds, "callCreds");
1✔
765
      if (error != null) {
1✔
766
        return this;
×
767
      }
768
      if (this.callCredentials != null) {
1✔
769
        callCreds = new CompositeCallCredentials(this.callCredentials, callCreds);
×
770
      }
771
      return new SslSocketFactoryResult(factory, callCreds, null);
1✔
772
    }
773
  }
774

775

776
  /**
777
   * Creates OkHttp transports. Exposed for internal use, as it should be private.
778
   */
779
  @Internal
780
  static final class OkHttpTransportFactory implements ClientTransportFactory {
781
    private final ObjectPool<Executor> executorPool;
782
    final Executor executor;
783
    private final ObjectPool<ScheduledExecutorService> scheduledExecutorServicePool;
784
    final ScheduledExecutorService scheduledExecutorService;
785
    final TransportTracer.Factory transportTracerFactory;
786
    final SocketFactory socketFactory;
787
    @Nullable final SSLSocketFactory sslSocketFactory;
788
    @Nullable
789
    final HostnameVerifier hostnameVerifier;
790
    final ConnectionSpec connectionSpec;
791
    final int maxMessageSize;
792
    private final boolean enableKeepAlive;
793
    private final long keepAliveTimeNanos;
794
    private final AtomicBackoff keepAliveBackoff;
795
    private final long keepAliveTimeoutNanos;
796
    final int flowControlWindow;
797
    private final boolean keepAliveWithoutCalls;
798
    final int maxInboundMetadataSize;
799
    final boolean useGetForSafeMethods;
800
    private final ChannelCredentials channelCredentials;
801
    private boolean closed;
802

803
    private OkHttpTransportFactory(
804
        ObjectPool<Executor> executorPool,
805
        ObjectPool<ScheduledExecutorService> scheduledExecutorServicePool,
806
        @Nullable SocketFactory socketFactory,
807
        @Nullable SSLSocketFactory sslSocketFactory,
808
        @Nullable HostnameVerifier hostnameVerifier,
809
        ConnectionSpec connectionSpec,
810
        int maxMessageSize,
811
        boolean enableKeepAlive,
812
        long keepAliveTimeNanos,
813
        long keepAliveTimeoutNanos,
814
        int flowControlWindow,
815
        boolean keepAliveWithoutCalls,
816
        int maxInboundMetadataSize,
817
        TransportTracer.Factory transportTracerFactory,
818
        boolean useGetForSafeMethods,
819
        ChannelCredentials channelCredentials) {
1✔
820
      this.executorPool = executorPool;
1✔
821
      this.executor = executorPool.getObject();
1✔
822
      this.scheduledExecutorServicePool = scheduledExecutorServicePool;
1✔
823
      this.scheduledExecutorService = scheduledExecutorServicePool.getObject();
1✔
824
      this.socketFactory = socketFactory;
1✔
825
      this.sslSocketFactory = sslSocketFactory;
1✔
826
      this.hostnameVerifier = hostnameVerifier;
1✔
827
      this.connectionSpec = connectionSpec;
1✔
828
      this.maxMessageSize = maxMessageSize;
1✔
829
      this.enableKeepAlive = enableKeepAlive;
1✔
830
      this.keepAliveTimeNanos = keepAliveTimeNanos;
1✔
831
      this.keepAliveBackoff = new AtomicBackoff("keepalive time nanos", keepAliveTimeNanos);
1✔
832
      this.keepAliveTimeoutNanos = keepAliveTimeoutNanos;
1✔
833
      this.flowControlWindow = flowControlWindow;
1✔
834
      this.keepAliveWithoutCalls = keepAliveWithoutCalls;
1✔
835
      this.maxInboundMetadataSize = maxInboundMetadataSize;
1✔
836
      this.useGetForSafeMethods = useGetForSafeMethods;
1✔
837
      this.channelCredentials = channelCredentials;
1✔
838

839
      this.transportTracerFactory =
1✔
840
          Preconditions.checkNotNull(transportTracerFactory, "transportTracerFactory");
1✔
841
    }
1✔
842

843
    @Override
844
    public ConnectionClientTransport newClientTransport(
845
        SocketAddress addr, ClientTransportOptions options, ChannelLogger channelLogger) {
846
      if (closed) {
1✔
847
        throw new IllegalStateException("The transport factory is closed.");
1✔
848
      }
849
      final AtomicBackoff.State keepAliveTimeNanosState = keepAliveBackoff.getState();
1✔
850
      Runnable tooManyPingsRunnable = new Runnable() {
1✔
851
        @Override
852
        public void run() {
853
          keepAliveTimeNanosState.backoff();
×
854
        }
×
855
      };
856
      InetSocketAddress inetSocketAddr = (InetSocketAddress) addr;
1✔
857
      // TODO(carl-mastrangelo): Pass channelLogger in.
858
      OkHttpClientTransport transport = new OkHttpClientTransport(
1✔
859
          this,
860
          inetSocketAddr,
861
          options.getAuthority(),
1✔
862
          options.getUserAgent(),
1✔
863
          options.getEagAttributes(),
1✔
864
          options.getHttpConnectProxiedSocketAddress(),
1✔
865
          tooManyPingsRunnable,
866
          channelCredentials);
867
      if (enableKeepAlive) {
1✔
868
        transport.enableKeepAlive(
×
869
            true, keepAliveTimeNanosState.get(), keepAliveTimeoutNanos, keepAliveWithoutCalls);
×
870
      }
871
      return transport;
1✔
872
    }
873

874
    @Override
875
    public ScheduledExecutorService getScheduledExecutorService() {
876
      return scheduledExecutorService;
1✔
877
    }
878

879
    @Nullable
880
    @CheckReturnValue
881
    @Override
882
    public SwapChannelCredentialsResult swapChannelCredentials(ChannelCredentials channelCreds) {
883
      SslSocketFactoryResult result = sslSocketFactoryFrom(channelCreds);
1✔
884
      if (result.error != null) {
1✔
885
        return null;
1✔
886
      }
887
      ClientTransportFactory factory = new OkHttpTransportFactory(
1✔
888
          executorPool,
889
          scheduledExecutorServicePool,
890
          socketFactory,
891
          result.factory,
892
          hostnameVerifier,
893
          connectionSpec,
894
          maxMessageSize,
895
          enableKeepAlive,
896
          keepAliveTimeNanos,
897
          keepAliveTimeoutNanos,
898
          flowControlWindow,
899
          keepAliveWithoutCalls,
900
          maxInboundMetadataSize,
901
          transportTracerFactory,
902
          useGetForSafeMethods,
903
          channelCredentials);
904
      return new SwapChannelCredentialsResult(factory, result.callCredentials);
1✔
905
    }
906

907
    @Override
908
    public void close() {
909
      if (closed) {
1✔
910
        return;
1✔
911
      }
912
      closed = true;
1✔
913

914
      executorPool.returnObject(executor);
1✔
915
      scheduledExecutorServicePool.returnObject(scheduledExecutorService);
1✔
916
    }
1✔
917

918
    @Override
919
    public Collection<Class<? extends SocketAddress>> getSupportedSocketAddressTypes() {
920
      return OkHttpChannelBuilder.getSupportedSocketAddressTypes();
1✔
921
    }
922
  }
923
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc