From fa36f83d4419c76e913f98894a091fb7e6aea826 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Mon, 18 Nov 2024 16:51:38 +0000 Subject: [PATCH 01/70] In-progress changes. --- .../io/grpc/netty/ProtocolNegotiators.java | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 84370ac8153..ddef7bd0c26 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -67,9 +67,15 @@ import io.netty.handler.ssl.SslProvider; import io.netty.util.AsciiString; import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.SocketAddress; import java.net.URI; import java.nio.channels.ClosedChannelException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.EnumSet; import java.util.Optional; @@ -82,6 +88,9 @@ import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; /** * Common {@link ProtocolNegotiator}s used by gRPC. @@ -99,7 +108,8 @@ final class ProtocolNegotiators { private ProtocolNegotiators() { } - public static FromChannelCredentialsResult from(ChannelCredentials creds) { + public static FromChannelCredentialsResult from(ChannelCredentials creds) + throws KeyStoreException, NoSuchAlgorithmException { if (creds instanceof TlsChannelCredentials) { TlsChannelCredentials tlsCreds = (TlsChannelCredentials) creds; Set incomprehensible = @@ -117,11 +127,20 @@ public static FromChannelCredentialsResult from(ChannelCredentials creds) { new ByteArrayInputStream(tlsCreds.getPrivateKey()), tlsCreds.getPrivateKeyPassword()); } + Optional x509ExtendedTrustManager; if (tlsCreds.getTrustManagers() != null) { builder.trustManager(new FixedTrustManagerFactory(tlsCreds.getTrustManagers())); + x509ExtendedTrustManager = tlsCreds.getTrustManagers().stream().filter( + trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); } else if (tlsCreds.getRootCertificates() != null) { builder.trustManager(new ByteArrayInputStream(tlsCreds.getRootCertificates())); - } // else use system default + } else { // else use system default + TrustManagerFactory tmf = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore)null); + x509ExtendedTrustManager = Arrays.stream(tmf.getTrustManagers()) + .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + } try { return FromChannelCredentialsResult.negotiator(tlsClientFactory(builder.build())); } catch (SSLException ex) { @@ -161,6 +180,26 @@ public static FromChannelCredentialsResult from(ChannelCredentials creds) { } } + private static X509ExtendedTrustManager getX509ExtendedTrustManager(InputStream rootCerts) throws GeneralSecurityException { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + try { + ks.load(null, null); + } catch (IOException ex) { + // Shouldn't really happen, as we're not loading any data. + throw new GeneralSecurityException(ex); + } + X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); + for (X509Certificate cert : certs) { + X500Principal principal = cert.getSubjectX500Principal(); + ks.setCertificateEntry(principal.getName("RFC2253"), cert); + } + + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(ks); + return trustManagerFactory.getTrustManagers(); + } + public static FromServerCredentialsResult from(ServerCredentials creds) { if (creds instanceof TlsServerCredentials) { TlsServerCredentials tlsCreds = (TlsServerCredentials) creds; @@ -711,17 +750,21 @@ public static ProtocolNegotiator tls(SslContext sslContext) { return tls(sslContext, null, Optional.empty()); } - public static ProtocolNegotiator.ClientFactory tlsClientFactory(SslContext sslContext) { - return new TlsProtocolNegotiatorClientFactory(sslContext); + public static ProtocolNegotiator.ClientFactory tlsClientFactory(SslContext sslContext, + X509ExtendedTrustManager x509ExtendedTrustManager) { + return new TlsProtocolNegotiatorClientFactory(sslContext, x509ExtendedTrustManager); } @VisibleForTesting static final class TlsProtocolNegotiatorClientFactory implements ProtocolNegotiator.ClientFactory { private final SslContext sslContext; + private final X509ExtendedTrustManager x509ExtendedTrustManager; - public TlsProtocolNegotiatorClientFactory(SslContext sslContext) { + public TlsProtocolNegotiatorClientFactory(SslContext sslContext, + X509ExtendedTrustManager x509ExtendedTrustManager) { this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext"); + this.x509ExtendedTrustManager = x509ExtendedTrustManager; } @Override public ProtocolNegotiator newNegotiator() { From f31b8bc917119cb812561f80cd55af55731cd002 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Tue, 26 Nov 2024 09:50:19 +0000 Subject: [PATCH 02/70] In-progress changes that used ExtendedSSLSession. --- examples/example-tls/build.gradle | 4 +- .../helloworldtls/HelloWorldClientTls.java | 5 +- netty/build.gradle | 3 +- .../netty/InternalProtocolNegotiators.java | 4 +- .../io/grpc/netty/NettyChannelBuilder.java | 2 +- .../io/grpc/netty/NettyClientTransport.java | 23 + .../NettySslContextChannelCredentials.java | 2 +- .../io/grpc/netty/ProtocolNegotiators.java | 403 ++++++++++++++++-- .../grpc/netty/NettyClientTransportTest.java | 6 +- .../grpc/netty/ProtocolNegotiatorsTest.java | 12 +- 10 files changed, 417 insertions(+), 47 deletions(-) diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 1eb51182309..c070cd3436e 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -74,8 +74,6 @@ application { applicationDistribution.into('bin') { from(helloWorldTlsServer) from(helloWorldTlsClient) - filePermissions { - unix(0755) - } + fileMode = 0755 } } diff --git a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java index 4ff7e23a299..2dd2646e623 100644 --- a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java +++ b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java @@ -16,6 +16,7 @@ package io.grpc.examples.helloworldtls; +import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.Grpc; import io.grpc.ManagedChannel; @@ -52,7 +53,9 @@ public void greet(String name) { HelloRequest request = HelloRequest.newBuilder().setName(name).build(); HelloReply response; try { - response = blockingStub.sayHello(request); + // response = blockingStub.sayHello(request); + response = io.grpc.stub.ClientCalls.blockingUnaryCall( + blockingStub.getChannel(), GreeterGrpc.getSayHelloMethod(), CallOptions.DEFAULT.withAuthority("localhost"), request); } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); return; diff --git a/netty/build.gradle b/netty/build.gradle index 5533038c85b..1a859a03bd6 100644 --- a/netty/build.gradle +++ b/netty/build.gradle @@ -18,7 +18,8 @@ tasks.named("jar").configure { dependencies { api project(':grpc-api'), libraries.netty.codec.http2 - implementation project(':grpc-core'), + implementation project(':grpc-util'), + project(':grpc-core'), libs.netty.handler.proxy, libraries.guava, libraries.errorprone.annotations, diff --git a/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java index 8aeb44d0fc2..b430bc05a35 100644 --- a/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java @@ -44,7 +44,7 @@ public static InternalProtocolNegotiator.ProtocolNegotiator tls(SslContext sslCo ObjectPool executorPool, Optional handshakeCompleteRunnable) { final io.grpc.netty.ProtocolNegotiator negotiator = ProtocolNegotiators.tls(sslContext, - executorPool, handshakeCompleteRunnable); + executorPool, handshakeCompleteRunnable, null); final class TlsNegotiator implements InternalProtocolNegotiator.ProtocolNegotiator { @Override @@ -170,7 +170,7 @@ public static ChannelHandler clientTlsHandler( ChannelHandler next, SslContext sslContext, String authority, ChannelLogger negotiationLogger) { return new ClientTlsHandler(next, sslContext, authority, null, negotiationLogger, - Optional.empty()); + Optional.empty(), null); } public static class ProtocolNegotiationHandler diff --git a/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java b/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java index fe226ec2ba9..a8721969b21 100644 --- a/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java +++ b/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java @@ -652,7 +652,7 @@ static ProtocolNegotiator createProtocolNegotiatorByType( case PLAINTEXT_UPGRADE: return ProtocolNegotiators.plaintextUpgrade(); case TLS: - return ProtocolNegotiators.tls(sslContext, executorPool, Optional.empty()); + return ProtocolNegotiators.tls(sslContext, executorPool, Optional.empty(), null); default: throw new IllegalArgumentException("Unsupported negotiationType: " + negotiationType); } diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index 54d1641a7ed..7a9eff5c917 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -45,6 +45,7 @@ import io.grpc.internal.StatsTraceContext; import io.grpc.internal.TransportTracer; import io.grpc.netty.NettyChannelBuilder.LocalSocketPicker; +import io.grpc.netty.ProtocolNegotiators.ClientTlsProtocolNegotiator; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFactory; @@ -60,15 +61,20 @@ import io.netty.util.concurrent.GenericFutureListener; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; +import java.security.cert.CertificateException; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nullable; +import javax.net.ssl.SSLPeerUnverifiedException; /** * A Netty-based {@link ConnectionClientTransport} implementation. */ class NettyClientTransport implements ConnectionClientTransport { + private static final Logger logger = Logger.getLogger(NettyClientTransport.class.getName()); private final InternalLogId logId; private final Map, ?> channelOptions; @@ -194,6 +200,23 @@ public ClientStream newStream( if (channel == null) { return new FailingClientStream(statusExplainingWhyTheChannelIsNull, tracers); } + if (negotiator instanceof ClientTlsProtocolNegotiator && callOptions.getAuthority() != null) { + ClientTlsProtocolNegotiator clientTlsProtocolNegotiator = + (ClientTlsProtocolNegotiator) negotiator; + if (!clientTlsProtocolNegotiator.canVerifyAuthorityOverride()) { + return new FailingClientStream(Status.INTERNAL.withDescription( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), + tracers); + } + try { + clientTlsProtocolNegotiator.verifyAuthorityAllowedForPeerCert(callOptions.getAuthority()); + } catch (SSLPeerUnverifiedException | CertificateException e) { + logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.", + callOptions.getAuthority()); + return new FailingClientStream(Status.INTERNAL.withDescription( + "Peer hostname verification failed for authority"), tracers); + } + } StatsTraceContext statsTraceCtx = StatsTraceContext.newClientContext(tracers, getAttributes(), headers); return new NettyClientStream( diff --git a/netty/src/main/java/io/grpc/netty/NettySslContextChannelCredentials.java b/netty/src/main/java/io/grpc/netty/NettySslContextChannelCredentials.java index ede511b68f6..3d3fdc67e8e 100644 --- a/netty/src/main/java/io/grpc/netty/NettySslContextChannelCredentials.java +++ b/netty/src/main/java/io/grpc/netty/NettySslContextChannelCredentials.java @@ -34,6 +34,6 @@ public static ChannelCredentials create(SslContext sslContext) { Preconditions.checkArgument(sslContext.isClient(), "Server SSL context can not be used for client channel"); GrpcSslContexts.ensureAlpnAndH2Enabled(sslContext.applicationProtocolNegotiator()); - return NettyChannelCredentials.create(ProtocolNegotiators.tlsClientFactory(sslContext)); + return NettyChannelCredentials.create(ProtocolNegotiators.tlsClientFactory(sslContext, null)); } } diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index ddef7bd0c26..9354d5112e3 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -44,6 +44,7 @@ import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; +import io.grpc.util.CertificateUtils; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; @@ -71,26 +72,38 @@ import java.io.InputStream; import java.net.SocketAddress; import java.net.URI; +import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.security.GeneralSecurityException; import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.Collections; import java.util.EnumSet; +import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; +import javax.net.ssl.ExtendedSSLSession; +import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; +import javax.security.auth.x500.X500Principal; /** * Common {@link ProtocolNegotiator}s used by gRPC. @@ -108,8 +121,7 @@ final class ProtocolNegotiators { private ProtocolNegotiators() { } - public static FromChannelCredentialsResult from(ChannelCredentials creds) - throws KeyStoreException, NoSuchAlgorithmException { + public static FromChannelCredentialsResult from(ChannelCredentials creds) { if (creds instanceof TlsChannelCredentials) { TlsChannelCredentials tlsCreds = (TlsChannelCredentials) creds; Set incomprehensible = @@ -128,22 +140,27 @@ public static FromChannelCredentialsResult from(ChannelCredentials creds) tlsCreds.getPrivateKeyPassword()); } Optional x509ExtendedTrustManager; - if (tlsCreds.getTrustManagers() != null) { - builder.trustManager(new FixedTrustManagerFactory(tlsCreds.getTrustManagers())); - x509ExtendedTrustManager = tlsCreds.getTrustManagers().stream().filter( - trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); - } else if (tlsCreds.getRootCertificates() != null) { - builder.trustManager(new ByteArrayInputStream(tlsCreds.getRootCertificates())); - } else { // else use system default - TrustManagerFactory tmf = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - tmf.init((KeyStore)null); - x509ExtendedTrustManager = Arrays.stream(tmf.getTrustManagers()) - .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); - } try { - return FromChannelCredentialsResult.negotiator(tlsClientFactory(builder.build())); - } catch (SSLException ex) { + if (tlsCreds.getTrustManagers() != null) { + builder.trustManager(new FixedTrustManagerFactory(tlsCreds.getTrustManagers())); + x509ExtendedTrustManager = tlsCreds.getTrustManagers().stream().filter( + trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + } else if (tlsCreds.getRootCertificates() != null) { + builder.trustManager(new ByteArrayInputStream(tlsCreds.getRootCertificates())); + x509ExtendedTrustManager = getX509ExtendedTrustManager(new ByteArrayInputStream( + tlsCreds.getRootCertificates())); + } else { // else use system default + TrustManagerFactory tmf = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + x509ExtendedTrustManager = Arrays.stream(tmf.getTrustManagers()) + .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + } + return FromChannelCredentialsResult.negotiator(tlsClientFactory(builder.build(), + x509ExtendedTrustManager.isPresent() + ? (X509ExtendedTrustManager) x509ExtendedTrustManager.get() + : null)); + } catch (SSLException | GeneralSecurityException ex) { log.log(Level.FINE, "Exception building SslContext", ex); return FromChannelCredentialsResult.error( "Unable to create SslContext: " + ex.getMessage()); @@ -180,7 +197,8 @@ public static FromChannelCredentialsResult from(ChannelCredentials creds) } } - private static X509ExtendedTrustManager getX509ExtendedTrustManager(InputStream rootCerts) throws GeneralSecurityException { + private static Optional getX509ExtendedTrustManager(InputStream rootCerts) + throws GeneralSecurityException { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); try { ks.load(null, null); @@ -197,7 +215,8 @@ private static X509ExtendedTrustManager getX509ExtendedTrustManager(InputStream TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(ks); - return trustManagerFactory.getTrustManagers(); + return Arrays.stream(trustManagerFactory.getTrustManagers()) + .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); } public static FromServerCredentialsResult from(ServerCredentials creds) { @@ -582,19 +601,25 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { + private SSLEngine sslEngine; + private SSLSession sslHandshakeSession; + public ClientTlsProtocolNegotiator(SslContext sslContext, - ObjectPool executorPool, Optional handshakeCompleteRunnable) { + ObjectPool executorPool, Optional handshakeCompleteRunnable, + X509ExtendedTrustManager x509ExtendedTrustManager) { this.sslContext = checkNotNull(sslContext, "sslContext"); this.executorPool = executorPool; if (this.executorPool != null) { this.executor = this.executorPool.getObject(); } this.handshakeCompleteRunnable = handshakeCompleteRunnable; + this.x509ExtendedTrustManager = x509ExtendedTrustManager; } private final SslContext sslContext; private final ObjectPool executorPool; private final Optional handshakeCompleteRunnable; + private final X509ExtendedTrustManager x509ExtendedTrustManager; private Executor executor; @Override @@ -607,7 +632,7 @@ public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) { ChannelHandler gnh = new GrpcNegotiationHandler(grpcHandler); ChannelLogger negotiationLogger = grpcHandler.getNegotiationLogger(); ChannelHandler cth = new ClientTlsHandler(gnh, sslContext, grpcHandler.getAuthority(), - this.executor, negotiationLogger, handshakeCompleteRunnable); + this.executor, negotiationLogger, handshakeCompleteRunnable, this); return new WaitUntilActiveHandler(cth, negotiationLogger); } @@ -617,6 +642,30 @@ public void close() { this.executorPool.returnObject(this.executor); } } + + boolean canVerifyAuthorityOverride() { + // sslEngine won't be set when creating ClientTlsHandlder from InternalProtocolNegotiators + // for example. + return sslEngine != null && x509ExtendedTrustManager != null; + } + + public void verifyAuthorityAllowedForPeerCert(String authority) + throws SSLPeerUnverifiedException, CertificateException { + SSLEngine sslEngineWrapper = new SSLEngineWrapper(sslEngine, authority); + // The typecasting of Certificate to X509Certificate should work because this method will only + // be called when there is a X509ExtendedTrustManager available. + Certificate[] peerCertificates = sslEngine.getSession().getPeerCertificates(); + X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; + for (int i = 0; i < peerCertificates.length; i++) { + x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; + } + x509ExtendedTrustManager.checkServerTrusted(x509PeerCertificates, "RSA", sslEngineWrapper); + } + + public void setSslEngine(SSLEngine sslEngine) { + this.sslEngine = sslEngine; + this.sslHandshakeSession = sslEngine.getHandshakeSession(); + } } static final class ClientTlsHandler extends ProtocolNegotiationHandler { @@ -624,12 +673,14 @@ static final class ClientTlsHandler extends ProtocolNegotiationHandler { private final SslContext sslContext; private final String host; private final int port; + private final ClientTlsProtocolNegotiator clientTlsProtocolNegotiator; private Executor executor; private final Optional handshakeCompleteRunnable; ClientTlsHandler(ChannelHandler next, SslContext sslContext, String authority, Executor executor, ChannelLogger negotiationLogger, - Optional handshakeCompleteRunnable) { + Optional handshakeCompleteRunnable, + ClientTlsProtocolNegotiator clientTlsProtocolNegotiator) { super(next, negotiationLogger); this.sslContext = checkNotNull(sslContext, "sslContext"); HostPort hostPort = parseAuthority(authority); @@ -637,6 +688,7 @@ static final class ClientTlsHandler extends ProtocolNegotiationHandler { this.port = hostPort.port; this.executor = executor; this.handshakeCompleteRunnable = handshakeCompleteRunnable; + this.clientTlsProtocolNegotiator = clientTlsProtocolNegotiator; } @Override @@ -648,6 +700,7 @@ protected void handlerAdded0(ChannelHandlerContext ctx) { ctx.pipeline().addBefore(ctx.name(), /* name= */ null, this.executor != null ? new SslHandler(sslEngine, false, this.executor) : new SslHandler(sslEngine, false)); + clientTlsProtocolNegotiator.setSslEngine(sslEngine); } @Override @@ -737,8 +790,10 @@ static HostPort parseAuthority(String authority) { * @param executorPool a dedicated {@link Executor} pool for time-consuming TLS tasks */ public static ProtocolNegotiator tls(SslContext sslContext, - ObjectPool executorPool, Optional handshakeCompleteRunnable) { - return new ClientTlsProtocolNegotiator(sslContext, executorPool, handshakeCompleteRunnable); + ObjectPool executorPool, Optional handshakeCompleteRunnable, + X509ExtendedTrustManager x509ExtendedTrustManager) { + return new ClientTlsProtocolNegotiator(sslContext, executorPool, handshakeCompleteRunnable, + x509ExtendedTrustManager); } /** @@ -746,8 +801,10 @@ public static ProtocolNegotiator tls(SslContext sslContext, * be negotiated, the {@code handler} is added and writes to the {@link io.netty.channel.Channel} * may happen immediately, even before the TLS Handshake is complete. */ - public static ProtocolNegotiator tls(SslContext sslContext) { - return tls(sslContext, null, Optional.empty()); + public static ProtocolNegotiator tls(SslContext sslContext, + X509ExtendedTrustManager x509ExtendedTrustManager) { + return tls(sslContext, null, Optional.empty(), + x509ExtendedTrustManager); } public static ProtocolNegotiator.ClientFactory tlsClientFactory(SslContext sslContext, @@ -768,7 +825,7 @@ public TlsProtocolNegotiatorClientFactory(SslContext sslContext, } @Override public ProtocolNegotiator newNegotiator() { - return tls(sslContext); + return tls(sslContext, x509ExtendedTrustManager); } @Override public int getDefaultPort() { @@ -1148,4 +1205,292 @@ protected final void fireProtocolNegotiationEvent(ChannelHandlerContext ctx) { ctx.fireUserEventTriggered(pne); } } + + static final class SSLEngineWrapper extends SSLEngine { + + private final SSLEngine sslEngine; + private final String peerHost; + + SSLEngineWrapper(SSLEngine sslEngine, String peerHost) { + this.sslEngine = sslEngine; + this.peerHost = peerHost; + } + + @Override + public String getPeerHost() { + return peerHost; + } + + @Override + public SSLSession getHandshakeSession() { + List statusResponses; + if (sslEngine.getHandshakeSession() instanceof ExtendedSSLSession) { + statusResponses = ((ExtendedSSLSession) sslEngine.getHandshakeSession()).getStatusResponses(); + } else { + statusResponses = Collections.emptyList(); + } + return new FakeExtendedSSLSession(peerHost, statusResponses); + } + + @Override + public SSLParameters getSSLParameters() { + return sslEngine.getSSLParameters(); + } + + @Override + public SSLEngineResult wrap(ByteBuffer[] byteBuffers, int i, int i1, ByteBuffer byteBuffer) + throws SSLException { + return null; + } + + @Override + public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers, int i, int i1) + throws SSLException { + return null; + } + + @Override + public Runnable getDelegatedTask() { + return null; + } + + @Override + public void closeInbound() throws SSLException { + + } + + @Override + public boolean isInboundDone() { + return false; + } + + @Override + public void closeOutbound() { + + } + + @Override + public boolean isOutboundDone() { + return false; + } + + @Override + public String[] getSupportedCipherSuites() { + return new String[0]; + } + + @Override + public String[] getEnabledCipherSuites() { + return new String[0]; + } + + @Override + public void setEnabledCipherSuites(String[] strings) { + + } + + @Override + public String[] getSupportedProtocols() { + return new String[0]; + } + + @Override + public String[] getEnabledProtocols() { + return new String[0]; + } + + @Override + public void setEnabledProtocols(String[] strings) { + + } + + @Override + public SSLSession getSession() { + return null; + } + + @Override + public void beginHandshake() throws SSLException { + + } + + @Override + public HandshakeStatus getHandshakeStatus() { + return null; + } + + @Override + public void setUseClientMode(boolean b) { + + } + + @Override + public boolean getUseClientMode() { + return false; + } + + @Override + public void setNeedClientAuth(boolean b) { + + } + + @Override + public boolean getNeedClientAuth() { + return false; + } + + @Override + public void setWantClientAuth(boolean b) { + + } + + @Override + public boolean getWantClientAuth() { + return false; + } + + @Override + public void setEnableSessionCreation(boolean b) { + + } + + @Override + public boolean getEnableSessionCreation() { + return false; + } + } + + static class FakeExtendedSSLSession extends ExtendedSSLSession { + private final String peerHost; + private final List statusResponses; + + FakeExtendedSSLSession(String peerHost, List statusResponses) { + this.peerHost = peerHost; + this.statusResponses = statusResponses; + } + + @Override + public String getPeerHost() { + return peerHost; + } + + @Override + public List getRequestedServerNames() { + return Collections.emptyList(); + } + + public List getStatusResponses() { + return statusResponses; + } + + @Override + public javax.security.cert.X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { + throw new UnsupportedOperationException("This method is deprecated and marked for removal. Use the getPeerCertificates() method instead."); + } + + @Override + public String[] getLocalSupportedSignatureAlgorithms() { + return new String[0]; + } + + @Override + public String[] getPeerSupportedSignatureAlgorithms() { + return new String[0]; + } + + @Override + public byte[] getId() { + return new byte[0]; + } + + @Override + public SSLSessionContext getSessionContext() { + return null; + } + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public long getLastAccessedTime() { + return 0; + } + + @Override + public void invalidate() { + + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public void putValue(String s, Object o) { + + } + + @Override + public Object getValue(String s) { + return null; + } + + @Override + public void removeValue(String s) { + + } + + @Override + public String[] getValueNames() { + return new String[0]; + } + + @Override + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { + return new Certificate[0]; + } + + @Override + public Certificate[] getLocalCertificates() { + return new Certificate[0]; + } + + @Override + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + return null; + } + + @Override + public Principal getLocalPrincipal() { + return null; + } + + @Override + public String getCipherSuite() { + return null; + } + + @Override + public String getProtocol() { + return null; + } + + @Override + public int getPeerPort() { + return 0; + } + + @Override + public int getPacketBufferSize() { + return 0; + } + + @Override + public int getApplicationBufferSize() { + return 0; + } + } } diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index b7a3ff13a59..e91a6c9b7e6 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -346,7 +346,7 @@ public void tlsNegotiationFailurePropagatesToStatus() throws Exception { .trustManager(caCert) .keyManager(clientCert, clientKey) .build(); - ProtocolNegotiator negotiator = ProtocolNegotiators.tls(clientContext); + ProtocolNegotiator negotiator = ProtocolNegotiators.tls(clientContext, null); final NettyClientTransport transport = newTransport(negotiator); callMeMaybe(transport.start(clientTransportListener)); @@ -803,7 +803,7 @@ public void tlsNegotiationServerExecutorShouldSucceed() throws Exception { .keyManager(clientCert, clientKey) .build(); ProtocolNegotiator negotiator = ProtocolNegotiators.tls(clientContext, clientExecutorPool, - Optional.empty()); + Optional.empty(), null); // after starting the client, the Executor in the client pool should be used assertEquals(true, clientExecutorPool.isInUse()); final NettyClientTransport transport = newTransport(negotiator); @@ -827,7 +827,7 @@ private Throwable getRootCause(Throwable t) { private ProtocolNegotiator newNegotiator() throws IOException { InputStream caCert = TlsTesting.loadCert("ca.pem"); SslContext clientContext = GrpcSslContexts.forClient().trustManager(caCert).build(); - return ProtocolNegotiators.tls(clientContext); + return ProtocolNegotiators.tls(clientContext, null); } private NettyClientTransport newTransport(ProtocolNegotiator negotiator) { diff --git a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java index 2ccdb2de543..7a5ea1a5cac 100644 --- a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java +++ b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java @@ -877,7 +877,7 @@ public String applicationProtocol() { DefaultEventLoopGroup elg = new DefaultEventLoopGroup(1); ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, - "authority", elg, noopLogger, Optional.empty()); + "authority", elg, noopLogger, Optional.empty(), null); pipeline.addLast(handler); pipeline.replace(SslHandler.class, null, goodSslHandler); pipeline.fireUserEventTriggered(ProtocolNegotiationEvent.DEFAULT); @@ -915,7 +915,7 @@ public String applicationProtocol() { .applicationProtocolConfig(apn).build(); ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, - "authority", elg, noopLogger, Optional.empty()); + "authority", elg, noopLogger, Optional.empty(), null); pipeline.addLast(handler); pipeline.replace(SslHandler.class, null, goodSslHandler); pipeline.fireUserEventTriggered(ProtocolNegotiationEvent.DEFAULT); @@ -939,7 +939,7 @@ public String applicationProtocol() { DefaultEventLoopGroup elg = new DefaultEventLoopGroup(1); ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, - "authority", elg, noopLogger, Optional.empty()); + "authority", elg, noopLogger, Optional.empty(), null); pipeline.addLast(handler); final AtomicReference error = new AtomicReference<>(); @@ -967,7 +967,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { @Test public void clientTlsHandler_closeDuringNegotiation() throws Exception { ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, - "authority", null, noopLogger, Optional.empty()); + "authority", null, noopLogger, Optional.empty(), null); pipeline.addLast(new WriteBufferingAndExceptionHandler(handler)); ChannelFuture pendingWrite = channel.writeAndFlush(NettyClientHandler.NOOP_MESSAGE); @@ -1007,7 +1007,7 @@ public boolean isLoggable(LogRecord record) { public void tls_failsOnNullSslContext() { thrown.expect(NullPointerException.class); - Object unused = ProtocolNegotiators.tls(null); + Object unused = ProtocolNegotiators.tls(null, null); } @Test @@ -1230,7 +1230,7 @@ public void clientTlsHandler_firesNegotiation() throws Exception { } FakeGrpcHttp2ConnectionHandler gh = FakeGrpcHttp2ConnectionHandler.newHandler(); ClientTlsProtocolNegotiator pn = new ClientTlsProtocolNegotiator(clientSslContext, - null, Optional.empty()); + null, Optional.empty(), null); WriteBufferingAndExceptionHandler clientWbaeh = new WriteBufferingAndExceptionHandler(pn.newHandler(gh)); From 0152478a71a24c95aa8019a02ef3f31d827a784f Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 27 Nov 2024 14:38:33 +0000 Subject: [PATCH 03/70] Authority verify in Netty transport. --- examples/example-tls/build.gradle | 1 + .../io/grpc/netty/ProtocolNegotiators.java | 53 +++++-------------- .../grpc/netty/NettyClientTransportTest.java | 2 +- 3 files changed, 14 insertions(+), 42 deletions(-) diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index c070cd3436e..f46b2919a5f 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -30,6 +30,7 @@ def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" + implementation "io.grpc:grpc-api:${grpcVersion}" compileOnly "org.apache.tomcat:annotations-api:6.0.53" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" } diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 9354d5112e3..8498d751f04 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -602,7 +602,6 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { private SSLEngine sslEngine; - private SSLSession sslHandshakeSession; public ClientTlsProtocolNegotiator(SslContext sslContext, ObjectPool executorPool, Optional handshakeCompleteRunnable, @@ -664,7 +663,6 @@ public void verifyAuthorityAllowedForPeerCert(String authority) public void setSslEngine(SSLEngine sslEngine) { this.sslEngine = sslEngine; - this.sslHandshakeSession = sslEngine.getHandshakeSession(); } } @@ -1223,13 +1221,7 @@ public String getPeerHost() { @Override public SSLSession getHandshakeSession() { - List statusResponses; - if (sslEngine.getHandshakeSession() instanceof ExtendedSSLSession) { - statusResponses = ((ExtendedSSLSession) sslEngine.getHandshakeSession()).getStatusResponses(); - } else { - statusResponses = Collections.emptyList(); - } - return new FakeExtendedSSLSession(peerHost, statusResponses); + return new FakeSSLSession(peerHost); } @Override @@ -1360,27 +1352,21 @@ public boolean getEnableSessionCreation() { } } - static class FakeExtendedSSLSession extends ExtendedSSLSession { + static class FakeSSLSession implements SSLSession { private final String peerHost; - private final List statusResponses; - FakeExtendedSSLSession(String peerHost, List statusResponses) { + FakeSSLSession(String peerHost) { this.peerHost = peerHost; - this.statusResponses = statusResponses; } @Override - public String getPeerHost() { - return peerHost; + public byte[] getId() { + return new byte[0]; } @Override - public List getRequestedServerNames() { - return Collections.emptyList(); - } - - public List getStatusResponses() { - return statusResponses; + public SSLSessionContext getSessionContext() { + return null; } @Override @@ -1388,26 +1374,6 @@ public javax.security.cert.X509Certificate[] getPeerCertificateChain() throws SS throw new UnsupportedOperationException("This method is deprecated and marked for removal. Use the getPeerCertificates() method instead."); } - @Override - public String[] getLocalSupportedSignatureAlgorithms() { - return new String[0]; - } - - @Override - public String[] getPeerSupportedSignatureAlgorithms() { - return new String[0]; - } - - @Override - public byte[] getId() { - return new byte[0]; - } - - @Override - public SSLSessionContext getSessionContext() { - return null; - } - @Override public long getCreationTime() { return 0; @@ -1478,6 +1444,11 @@ public String getProtocol() { return null; } + @Override + public String getPeerHost() { + return peerHost; + } + @Override public int getPeerPort() { return 0; diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index e91a6c9b7e6..ad18844e5ce 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -951,7 +951,7 @@ private static class Rpc { Rpc(NettyClientTransport transport, Metadata headers) { stream = transport.newStream( - METHOD, headers, CallOptions.DEFAULT, + METHOD, headers, CallOptions.DEFAULT.withAuthority("wrong-authority"), new ClientStreamTracer[]{ new ClientStreamTracer() {} }); stream.start(listener); stream.request(1); From c31995e4b6c12554bb79e8d214bca229f5f3e3fa Mon Sep 17 00:00:00 2001 From: Kannan J Date: Fri, 29 Nov 2024 06:35:52 +0000 Subject: [PATCH 04/70] In-progress changes for Authority verify in okhttp transport. --- .../internal/ManagedChannelImplBuilder.java | 4 +++ .../io/grpc/okhttp/OkHttpChannelBuilder.java | 16 ++++++--- .../io/grpc/okhttp/OkHttpClientTransport.java | 33 +++++++++++++++---- .../okhttp/OkHttpClientTransportTest.java | 18 +++++----- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java b/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java index 48a255472e1..180d80d0d44 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java @@ -360,6 +360,10 @@ public ManagedChannelImplBuilder(SocketAddress directServerAddress, String autho InternalConfiguratorRegistry.configureChannelBuilder(this); } + public ChannelCredentials getChannelCredentials() { + return channelCredentials; + } + @Override public ManagedChannelImplBuilder directExecutor() { return executor(MoreExecutors.directExecutor()); diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java index 15508110344..15c84708474 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java @@ -47,6 +47,7 @@ import io.grpc.internal.SharedResourceHolder.Resource; import io.grpc.internal.SharedResourcePool; import io.grpc.internal.TransportTracer; +import io.grpc.internal.TransportTracer.Factory; import io.grpc.okhttp.internal.CipherSuite; import io.grpc.okhttp.internal.ConnectionSpec; import io.grpc.okhttp.internal.Platform; @@ -536,7 +537,8 @@ OkHttpTransportFactory buildTransportFactory() { keepAliveWithoutCalls, maxInboundMetadataSize, transportTracerFactory, - useGetForSafeMethods); + useGetForSafeMethods, + managedChannelImplBuilder.getChannelCredentials()); } OkHttpChannelBuilder disableCheckAuthority() { @@ -799,6 +801,7 @@ static final class OkHttpTransportFactory implements ClientTransportFactory { private final boolean keepAliveWithoutCalls; final int maxInboundMetadataSize; final boolean useGetForSafeMethods; + private final ChannelCredentials channelCredentials; private boolean closed; private OkHttpTransportFactory( @@ -815,8 +818,9 @@ private OkHttpTransportFactory( int flowControlWindow, boolean keepAliveWithoutCalls, int maxInboundMetadataSize, - TransportTracer.Factory transportTracerFactory, - boolean useGetForSafeMethods) { + Factory transportTracerFactory, + boolean useGetForSafeMethods, + ChannelCredentials channelCredentials) { this.executorPool = executorPool; this.executor = executorPool.getObject(); this.scheduledExecutorServicePool = scheduledExecutorServicePool; @@ -834,6 +838,7 @@ private OkHttpTransportFactory( this.keepAliveWithoutCalls = keepAliveWithoutCalls; this.maxInboundMetadataSize = maxInboundMetadataSize; this.useGetForSafeMethods = useGetForSafeMethods; + this.channelCredentials = channelCredentials; this.transportTracerFactory = Preconditions.checkNotNull(transportTracerFactory, "transportTracerFactory"); @@ -861,7 +866,8 @@ public void run() { options.getUserAgent(), options.getEagAttributes(), options.getHttpConnectProxiedSocketAddress(), - tooManyPingsRunnable); + tooManyPingsRunnable, + channelCredentials); if (enableKeepAlive) { transport.enableKeepAlive( true, keepAliveTimeNanosState.get(), keepAliveTimeoutNanos, keepAliveWithoutCalls); @@ -897,7 +903,7 @@ public SwapChannelCredentialsResult swapChannelCredentials(ChannelCredentials ch keepAliveWithoutCalls, maxInboundMetadataSize, transportTracerFactory, - useGetForSafeMethods); + useGetForSafeMethods, managedChannelImplBuilder.getChannelCredentials()); return new SwapChannelCredentialsResult(factory, result.callCredentials); } diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 59f824b1a3c..3b861d23d28 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -29,6 +29,7 @@ import com.google.common.util.concurrent.SettableFuture; import io.grpc.Attributes; import io.grpc.CallOptions; +import io.grpc.ChannelCredentials; import io.grpc.ClientStreamTracer; import io.grpc.Grpc; import io.grpc.HttpConnectProxiedSocketAddress; @@ -42,6 +43,8 @@ import io.grpc.Status; import io.grpc.Status.Code; import io.grpc.StatusException; +import io.grpc.TlsChannelCredentials; +import io.grpc.internal.ClientStream; import io.grpc.internal.ClientStreamListener.RpcProgress; import io.grpc.internal.ConnectionClientTransport; import io.grpc.internal.GrpcAttributes; @@ -54,6 +57,7 @@ import io.grpc.internal.StatsTraceContext; import io.grpc.internal.TransportTracer; import io.grpc.okhttp.ExceptionHandlingFrameWriter.TransportExceptionHandler; +import io.grpc.okhttp.OkHttpChannelBuilder.OkHttpTransportFactory; import io.grpc.okhttp.internal.ConnectionSpec; import io.grpc.okhttp.internal.Credentials; import io.grpc.okhttp.internal.StatusLine; @@ -82,6 +86,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; @@ -99,6 +104,7 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509ExtendedTrustManager; import okio.Buffer; import okio.BufferedSink; import okio.BufferedSource; @@ -114,6 +120,7 @@ class OkHttpClientTransport implements ConnectionClientTransport, TransportExcep OutboundFlowController.Transport { private static final Map ERROR_CODE_TO_STATUS = buildErrorCodeToStatusMap(); private static final Logger log = Logger.getLogger(OkHttpClientTransport.class.getName()); + private final ChannelCredentials channelCredentials; private static Map buildErrorCodeToStatusMap() { Map errorToStatus = new EnumMap<>(ErrorCode.class); @@ -205,6 +212,8 @@ private static Map buildErrorCodeToStatusMap() { private final boolean useGetForSafeMethods; @GuardedBy("lock") private final TransportTracer transportTracer; + private Optional x509ExtendedTrustManager; + @GuardedBy("lock") private final InUseStateAggregator inUseState = new InUseStateAggregator() { @@ -233,13 +242,14 @@ protected void handleNotInUse() { SettableFuture connectedFuture; public OkHttpClientTransport( - OkHttpChannelBuilder.OkHttpTransportFactory transportFactory, + OkHttpTransportFactory transportFactory, InetSocketAddress address, String authority, @Nullable String userAgent, Attributes eagAttrs, @Nullable HttpConnectProxiedSocketAddress proxiedAddr, - Runnable tooManyPingsRunnable) { + Runnable tooManyPingsRunnable, + ChannelCredentials channelCredentials) { this( transportFactory, address, @@ -249,11 +259,12 @@ public OkHttpClientTransport( GrpcUtil.STOPWATCH_SUPPLIER, new Http2(), proxiedAddr, - tooManyPingsRunnable); + tooManyPingsRunnable, + channelCredentials); } private OkHttpClientTransport( - OkHttpChannelBuilder.OkHttpTransportFactory transportFactory, + OkHttpTransportFactory transportFactory, InetSocketAddress address, String authority, @Nullable String userAgent, @@ -261,7 +272,8 @@ private OkHttpClientTransport( Supplier stopwatchFactory, Variant variant, @Nullable HttpConnectProxiedSocketAddress proxiedAddr, - Runnable tooManyPingsRunnable) { + Runnable tooManyPingsRunnable, + ChannelCredentials channelCredentials) { this.address = Preconditions.checkNotNull(address, "address"); this.defaultAuthority = authority; this.maxMessageSize = transportFactory.maxMessageSize; @@ -291,6 +303,7 @@ private OkHttpClientTransport( this.attributes = Attributes.newBuilder() .set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, eagAttrs).build(); this.useGetForSafeMethods = transportFactory.useGetForSafeMethods; + this.channelCredentials = channelCredentials; initTransportTracer(); } @@ -316,7 +329,8 @@ private OkHttpClientTransport( stopwatchFactory, variant, null, - tooManyPingsRunnable); + tooManyPingsRunnable, + null); this.connectingCallback = connectingCallback; this.connectedFuture = Preconditions.checkNotNull(connectedFuture, "connectedFuture"); } @@ -389,13 +403,18 @@ public void ping(final PingCallback callback, Executor executor) { } @Override - public OkHttpClientStream newStream( + public ClientStream newStream( MethodDescriptor method, Metadata headers, CallOptions callOptions, ClientStreamTracer[] tracers) { Preconditions.checkNotNull(method, "method"); Preconditions.checkNotNull(headers, "headers"); StatsTraceContext statsTraceContext = StatsTraceContext.newClientContext(tracers, getAttributes(), headers); + if (callOptions.getAuthority() != null && channelCredentials instanceof TlsChannelCredentials) { + if (x509ExtendedTrustManager == null) { + + } + } // FIXME: it is likely wrong to pass the transportTracer here as it'll exit the lock's scope synchronized (lock) { // to make @GuardedBy linter happy return new OkHttpClientStream( diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java index daf5073992e..0148551febe 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java @@ -241,7 +241,7 @@ public void testToString() throws Exception { /*userAgent=*/ null, EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable); + tooManyPingsRunnable, channelCredentials); String s = clientTransport.toString(); assertTrue("Unexpected: " + s, s.contains("OkHttpClientTransport")); assertTrue("Unexpected: " + s, s.contains(address.toString())); @@ -259,7 +259,7 @@ public void testTransportExecutorWithTooFewThreads() throws Exception { null, EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable); + tooManyPingsRunnable, channelCredentials); clientTransport.start(transportListener); ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); verify(transportListener, timeout(TIME_OUT_MS)).transportShutdown(statusCaptor.capture()); @@ -1726,7 +1726,7 @@ public void invalidAuthorityPropagates() { "userAgent", EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable); + tooManyPingsRunnable, channelCredentials); String host = clientTransport.getOverridenHost(); int port = clientTransport.getOverridenPort(); @@ -1744,7 +1744,7 @@ public void unreachableServer() throws Exception { "userAgent", EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable); + tooManyPingsRunnable, channelCredentials); ManagedClientTransport.Listener listener = mock(ManagedClientTransport.Listener.class); clientTransport.start(listener); @@ -1774,7 +1774,7 @@ public void customSocketFactory() throws Exception { "userAgent", EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable); + tooManyPingsRunnable, channelCredentials); ManagedClientTransport.Listener listener = mock(ManagedClientTransport.Listener.class); clientTransport.start(listener); @@ -1799,7 +1799,7 @@ public void proxy_200() throws Exception { .setTargetAddress(targetAddress) .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) .build(), - tooManyPingsRunnable); + tooManyPingsRunnable, channelCredentials); clientTransport.start(transportListener); Socket sock = serverSocket.accept(); @@ -1848,7 +1848,7 @@ public void proxy_500() throws Exception { .setTargetAddress(targetAddress) .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) .build(), - tooManyPingsRunnable); + tooManyPingsRunnable, channelCredentials); clientTransport.start(transportListener); Socket sock = serverSocket.accept(); @@ -1896,7 +1896,7 @@ public void proxy_immediateServerClose() throws Exception { .setTargetAddress(targetAddress) .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) .build(), - tooManyPingsRunnable); + tooManyPingsRunnable, channelCredentials); clientTransport.start(transportListener); Socket sock = serverSocket.accept(); @@ -1927,7 +1927,7 @@ public void proxy_serverHangs() throws Exception { .setTargetAddress(targetAddress) .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) .build(), - tooManyPingsRunnable); + tooManyPingsRunnable, channelCredentials); clientTransport.proxySocketTimeout = 10; clientTransport.start(transportListener); From 5e2e22e7cebf82db1f4cd762386dcf3067497804 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Sat, 30 Nov 2024 16:03:26 +0000 Subject: [PATCH 05/70] Netty authority verify against peer host in server cert. --- .../io/grpc/netty/ProtocolNegotiators.java | 70 ++++----- .../grpc/netty/NettyClientTransportTest.java | 137 +++++++++++++++++- .../grpc/netty/ProtocolNegotiatorsTest.java | 63 +++++++- 3 files changed, 227 insertions(+), 43 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 8498d751f04..dbaa9b5fd95 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -81,17 +81,13 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; -import java.util.Collections; import java.util.EnumSet; -import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; -import javax.net.ssl.ExtendedSSLSession; -import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; @@ -197,28 +193,6 @@ public static FromChannelCredentialsResult from(ChannelCredentials creds) { } } - private static Optional getX509ExtendedTrustManager(InputStream rootCerts) - throws GeneralSecurityException { - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - try { - ks.load(null, null); - } catch (IOException ex) { - // Shouldn't really happen, as we're not loading any data. - throw new GeneralSecurityException(ex); - } - X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); - for (X509Certificate cert : certs) { - X500Principal principal = cert.getSubjectX500Principal(); - ks.setCertificateEntry(principal.getName("RFC2253"), cert); - } - - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(ks); - return Arrays.stream(trustManagerFactory.getTrustManagers()) - .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); - } - public static FromServerCredentialsResult from(ServerCredentials creds) { if (creds instanceof TlsServerCredentials) { TlsServerCredentials tlsCreds = (TlsServerCredentials) creds; @@ -297,6 +271,28 @@ public static FromServerCredentialsResult from(ServerCredentials creds) { } } + private static Optional getX509ExtendedTrustManager(InputStream rootCerts) + throws GeneralSecurityException { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + try { + ks.load(null, null); + } catch (IOException ex) { + // Shouldn't really happen, as we're not loading any data. + throw new GeneralSecurityException(ex); + } + X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); + for (X509Certificate cert : certs) { + X500Principal principal = cert.getSubjectX500Principal(); + ks.setCertificateEntry(principal.getName("RFC2253"), cert); + } + + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(ks); + return Arrays.stream(trustManagerFactory.getTrustManagers()) + .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + } + public static final class FromChannelCredentialsResult { public final ProtocolNegotiator.ClientFactory negotiator; public final CallCredentials callCredentials; @@ -650,7 +646,7 @@ boolean canVerifyAuthorityOverride() { public void verifyAuthorityAllowedForPeerCert(String authority) throws SSLPeerUnverifiedException, CertificateException { - SSLEngine sslEngineWrapper = new SSLEngineWrapper(sslEngine, authority); + SSLEngine sslEngineWrapper = new SslEngineWrapper(sslEngine, authority); // The typecasting of Certificate to X509Certificate should work because this method will only // be called when there is a X509ExtendedTrustManager available. Certificate[] peerCertificates = sslEngine.getSession().getPeerCertificates(); @@ -664,6 +660,11 @@ public void verifyAuthorityAllowedForPeerCert(String authority) public void setSslEngine(SSLEngine sslEngine) { this.sslEngine = sslEngine; } + + @VisibleForTesting + boolean hasX509ExtendedTrustManager() { + return x509ExtendedTrustManager != null; + } } static final class ClientTlsHandler extends ProtocolNegotiationHandler { @@ -1204,12 +1205,12 @@ protected final void fireProtocolNegotiationEvent(ChannelHandlerContext ctx) { } } - static final class SSLEngineWrapper extends SSLEngine { + static final class SslEngineWrapper extends SSLEngine { private final SSLEngine sslEngine; private final String peerHost; - SSLEngineWrapper(SSLEngine sslEngine, String peerHost) { + SslEngineWrapper(SSLEngine sslEngine, String peerHost) { this.sslEngine = sslEngine; this.peerHost = peerHost; } @@ -1221,7 +1222,7 @@ public String getPeerHost() { @Override public SSLSession getHandshakeSession() { - return new FakeSSLSession(peerHost); + return new FakeSslSession(peerHost); } @Override @@ -1352,10 +1353,10 @@ public boolean getEnableSessionCreation() { } } - static class FakeSSLSession implements SSLSession { + static class FakeSslSession implements SSLSession { private final String peerHost; - FakeSSLSession(String peerHost) { + FakeSslSession(String peerHost) { this.peerHost = peerHost; } @@ -1370,8 +1371,9 @@ public SSLSessionContext getSessionContext() { } @Override - public javax.security.cert.X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { - throw new UnsupportedOperationException("This method is deprecated and marked for removal. Use the getPeerCertificates() method instead."); + public javax.security.cert.X509Certificate[] getPeerCertificateChain() { + throw new UnsupportedOperationException("This method is deprecated and marked for removal. " + + "Use the getPeerCertificates() method instead."); } @Override diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index ad18844e5ce..da4760e853c 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -59,9 +59,11 @@ import io.grpc.internal.ClientStream; import io.grpc.internal.ClientStreamListener; import io.grpc.internal.ClientTransport; +import io.grpc.internal.FailingClientStream; import io.grpc.internal.FakeClock; import io.grpc.internal.FixedObjectPool; import io.grpc.internal.GrpcUtil; +import io.grpc.internal.InsightBuilder; import io.grpc.internal.ManagedClientTransport; import io.grpc.internal.ServerListener; import io.grpc.internal.ServerStream; @@ -73,6 +75,7 @@ import io.grpc.netty.NettyChannelBuilder.LocalSocketPicker; import io.grpc.netty.NettyTestUtil.TrackingObjectPoolForTest; import io.grpc.testing.TlsTesting; +import io.grpc.util.CertificateUtils; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelConfig; @@ -100,7 +103,12 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -114,6 +122,11 @@ import javax.annotation.Nullable; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -199,7 +212,7 @@ public void addDefaultUserAgent() throws Exception { } @Test - public void setSoLingerChannelOption() throws IOException { + public void setSoLingerChannelOption() throws IOException, GeneralSecurityException { startServer(); Map, Object> channelOptions = new HashMap<>(); // set SO_LINGER option @@ -817,6 +830,51 @@ public void tlsNegotiationServerExecutorShouldSucceed() throws Exception { assertEquals(false, serverExecutorPool.isInUse()); } + @Test + public void authorityOverrideInCallOptions_doesntMatchServerPeerHost_newStreamCreationFails() + throws IOException, InterruptedException, GeneralSecurityException { + startServer(); + NettyClientTransport transport = newTransport(newNegotiator()); + FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); + callMeMaybe(transport.start(fakeClientTransportListener)); + synchronized (fakeClientTransportListener) { + fakeClientTransportListener.wait(10000); + } + assertThat(fakeClientTransportListener.isConnected).isTrue(); + + ClientStream stream = transport.newStream( + Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), + new ClientStreamTracer[]{new ClientStreamTracer() { + }}); + + assertThat(stream).isInstanceOf(FailingClientStream.class); + InsightBuilder insightBuilder = new InsightBuilder(); + stream.appendTimeoutInsight(insightBuilder); + assertThat(insightBuilder.toString()).contains( + "Status{code=INTERNAL, description=Peer hostname verification failed for authority, " + + "cause=null}"); + } + + @Test + public void authorityOverrideInCallOptions_matchesServerPeerHost_newStreamCreationSucceeds() + throws IOException, InterruptedException, GeneralSecurityException { + startServer(); + NettyClientTransport transport = newTransport(newNegotiator()); + FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); + callMeMaybe(transport.start(fakeClientTransportListener)); + synchronized (fakeClientTransportListener) { + fakeClientTransportListener.wait(10000); + } + assertThat(fakeClientTransportListener.isConnected).isTrue(); + + ClientStream stream = transport.newStream( + Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("zoo.test.google.fr"), + new ClientStreamTracer[]{new ClientStreamTracer() { + }}); + + assertThat(stream).isNotInstanceOf(FailingClientStream.class); + } + private Throwable getRootCause(Throwable t) { if (t.getCause() == null) { return t; @@ -824,10 +882,34 @@ private Throwable getRootCause(Throwable t) { return getRootCause(t.getCause()); } - private ProtocolNegotiator newNegotiator() throws IOException { + private ProtocolNegotiator newNegotiator() throws IOException, GeneralSecurityException { InputStream caCert = TlsTesting.loadCert("ca.pem"); SslContext clientContext = GrpcSslContexts.forClient().trustManager(caCert).build(); - return ProtocolNegotiators.tls(clientContext, null); + return ProtocolNegotiators.tls(clientContext, + (X509ExtendedTrustManager) getX509ExtendedTrustManager( + TlsTesting.loadCert("ca.pem")).get()); + } + + private static Optional getX509ExtendedTrustManager(InputStream rootCerts) + throws GeneralSecurityException { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + try { + ks.load(null, null); + } catch (IOException ex) { + // Shouldn't really happen, as we're not loading any data. + throw new GeneralSecurityException(ex); + } + X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); + for (X509Certificate cert : certs) { + X500Principal principal = cert.getSubjectX500Principal(); + ks.setCertificateEntry(principal.getName("RFC2253"), cert); + } + + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(ks); + return Arrays.stream(trustManagerFactory.getTrustManagers()) + .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); } private NettyClientTransport newTransport(ProtocolNegotiator negotiator) { @@ -951,7 +1033,7 @@ private static class Rpc { Rpc(NettyClientTransport transport, Metadata headers) { stream = transport.newStream( - METHOD, headers, CallOptions.DEFAULT.withAuthority("wrong-authority"), + METHOD, headers, CallOptions.DEFAULT, new ClientStreamTracer[]{ new ClientStreamTracer() {} }); stream.start(listener); stream.request(1); @@ -1144,4 +1226,51 @@ public void log(ChannelLogLevel level, String message) {} @Override public void log(ChannelLogLevel level, String messageFormat, Object... args) {} } + + static class FakeTrustManager implements X509TrustManager { + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) + throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) + throws CertificateException { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + + static class FakeClientTransportListener implements ManagedClientTransport.Listener { + private boolean isConnected = false; + + @Override + public void transportShutdown(Status s) { + + } + + @Override + public void transportTerminated() { + + } + + @Override + public void transportReady() { + isConnected = true; + synchronized (this) { + notify(); + } + } + + @Override + public void transportInUse(boolean inUse) { + + } + } } diff --git a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java index 7a5ea1a5cac..cba3757e741 100644 --- a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java +++ b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java @@ -111,10 +111,14 @@ import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandshakeCompletionEvent; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayDeque; import java.util.Arrays; @@ -222,13 +226,52 @@ public ChannelCredentials withoutBearerTokens() { } @Test - public void fromClient_tls() { + public void fromClient_tls_trustManager() + throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException { + KeyStore certStore = KeyStore.getInstance(KeyStore.getDefaultType()); + certStore.load(null); + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + try (InputStream ca = TlsTesting.loadCert("ca.pem")) { + for (X509Certificate cert : CertificateUtils.getX509Certificates(ca)) { + certStore.setCertificateEntry(cert.getSubjectX500Principal().getName("RFC2253"), cert); + } + } + trustManagerFactory.init(certStore); + ProtocolNegotiators.FromChannelCredentialsResult result = + ProtocolNegotiators.from(TlsChannelCredentials.newBuilder() + .trustManager(trustManagerFactory.getTrustManagers()).build()); + assertThat(result.error).isNull(); + assertThat(result.callCredentials).isNull(); + assertThat(result.negotiator) + .isInstanceOf(ProtocolNegotiators.TlsProtocolNegotiatorClientFactory.class); + assertThat(((ClientTlsProtocolNegotiator) result.negotiator.newNegotiator()) + .hasX509ExtendedTrustManager()).isTrue(); + } + + @Test + public void fromClient_tls_CaCertsInputStream() throws IOException { + ProtocolNegotiators.FromChannelCredentialsResult result = + ProtocolNegotiators.from(TlsChannelCredentials.newBuilder() + .trustManager(TlsTesting.loadCert("ca.pem")).build()); + assertThat(result.error).isNull(); + assertThat(result.callCredentials).isNull(); + assertThat(result.negotiator) + .isInstanceOf(ProtocolNegotiators.TlsProtocolNegotiatorClientFactory.class); + assertThat(((ClientTlsProtocolNegotiator) result.negotiator.newNegotiator()) + .hasX509ExtendedTrustManager()).isTrue(); + } + + @Test + public void fromClient_tls_systemDefault() { ProtocolNegotiators.FromChannelCredentialsResult result = ProtocolNegotiators.from(TlsChannelCredentials.create()); assertThat(result.error).isNull(); assertThat(result.callCredentials).isNull(); assertThat(result.negotiator) .isInstanceOf(ProtocolNegotiators.TlsProtocolNegotiatorClientFactory.class); + assertThat(((ClientTlsProtocolNegotiator) result.negotiator.newNegotiator()) + .hasX509ExtendedTrustManager()).isTrue(); } @Test @@ -877,7 +920,8 @@ public String applicationProtocol() { DefaultEventLoopGroup elg = new DefaultEventLoopGroup(1); ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, - "authority", elg, noopLogger, Optional.empty(), null); + "authority", elg, noopLogger, Optional.empty(), + getClientTlsProtocolNegotiator()); pipeline.addLast(handler); pipeline.replace(SslHandler.class, null, goodSslHandler); pipeline.fireUserEventTriggered(ProtocolNegotiationEvent.DEFAULT); @@ -915,7 +959,8 @@ public String applicationProtocol() { .applicationProtocolConfig(apn).build(); ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, - "authority", elg, noopLogger, Optional.empty(), null); + "authority", elg, noopLogger, Optional.empty(), + getClientTlsProtocolNegotiator()); pipeline.addLast(handler); pipeline.replace(SslHandler.class, null, goodSslHandler); pipeline.fireUserEventTriggered(ProtocolNegotiationEvent.DEFAULT); @@ -939,7 +984,8 @@ public String applicationProtocol() { DefaultEventLoopGroup elg = new DefaultEventLoopGroup(1); ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, - "authority", elg, noopLogger, Optional.empty(), null); + "authority", elg, noopLogger, Optional.empty(), + getClientTlsProtocolNegotiator()); pipeline.addLast(handler); final AtomicReference error = new AtomicReference<>(); @@ -967,7 +1013,8 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { @Test public void clientTlsHandler_closeDuringNegotiation() throws Exception { ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, - "authority", null, noopLogger, Optional.empty(), null); + "authority", null, noopLogger, Optional.empty(), + getClientTlsProtocolNegotiator()); pipeline.addLast(new WriteBufferingAndExceptionHandler(handler)); ChannelFuture pendingWrite = channel.writeAndFlush(NettyClientHandler.NOOP_MESSAGE); @@ -979,6 +1026,12 @@ public void clientTlsHandler_closeDuringNegotiation() throws Exception { .isEqualTo(Status.Code.UNAVAILABLE); } + private ClientTlsProtocolNegotiator getClientTlsProtocolNegotiator() throws SSLException { + return new ClientTlsProtocolNegotiator(GrpcSslContexts.forClient().trustManager( + TlsTesting.loadCert("ca.pem")).build(), + null, Optional.empty(), null); + } + @Test public void engineLog() { ChannelHandler handler = new ServerTlsHandler(grpcHandler, sslContext, null); From b0f86cfb40055f71e364ba8d2f0a9d49cb5375fd Mon Sep 17 00:00:00 2001 From: Kannan J Date: Sun, 1 Dec 2024 08:41:28 +0000 Subject: [PATCH 06/70] unit test. --- .../grpc/netty/NettyClientTransportTest.java | 86 ++++++++++++++----- 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index da4760e853c..aeb1a835ebb 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -56,6 +56,7 @@ import io.grpc.Status; import io.grpc.Status.Code; import io.grpc.StatusException; +import io.grpc.TlsChannelCredentials; import io.grpc.internal.ClientStream; import io.grpc.internal.ClientStreamListener; import io.grpc.internal.ClientTransport; @@ -830,6 +831,45 @@ public void tlsNegotiationServerExecutorShouldSucceed() throws Exception { assertEquals(false, serverExecutorPool.isInUse()); } + /** + * This test tests the case of TlsCredentials passed to ProtocolNegotiators not having an instance + * of X509ExtendedTrustManager (this is not testable in ProtocolNegotiatorsTest without creating + * accessors for the internal state of negotiator whether it has a X509ExtendedTrustManager, + * hence the need to test it in this class instead). To establish a successful handshake we create + * a fake X509TrustManager not implementing X509ExtendedTrustManager but wraps the real + * X509ExtendedTrustManager. + */ + @Test + public void authorityOverrideInCallOptions_noX509ExtendedTrustManager_newStreamCreationFails() + throws IOException, InterruptedException, GeneralSecurityException { + startServer(); + InputStream caCert = TlsTesting.loadCert("ca.pem"); + X509TrustManager x509ExtendedTrustManager = + (X509TrustManager) getX509ExtendedTrustManager(caCert).get(); + ProtocolNegotiators.FromChannelCredentialsResult result = + ProtocolNegotiators.from(TlsChannelCredentials.newBuilder() + .trustManager(new FakeTrustManager(x509ExtendedTrustManager)).build()); + NettyClientTransport transport = newTransport(result.negotiator.newNegotiator()); + FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); + callMeMaybe(transport.start(fakeClientTransportListener)); + synchronized (fakeClientTransportListener) { + fakeClientTransportListener.wait(10000); + } + assertThat(fakeClientTransportListener.isConnected).isTrue(); + + ClientStream stream = transport.newStream( + Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), + new ClientStreamTracer[]{new ClientStreamTracer() { + }}); + + assertThat(stream).isInstanceOf(FailingClientStream.class); + InsightBuilder insightBuilder = new InsightBuilder(); + stream.appendTimeoutInsight(insightBuilder); + assertThat(insightBuilder.toString()).contains( + "Status{code=INTERNAL, description=Can't allow authority override in rpc when " + + "X509ExtendedTrustManager is not available, cause=null}"); + } + @Test public void authorityOverrideInCallOptions_doesntMatchServerPeerHost_newStreamCreationFails() throws IOException, InterruptedException, GeneralSecurityException { @@ -1227,26 +1267,6 @@ public void log(ChannelLogLevel level, String message) {} public void log(ChannelLogLevel level, String messageFormat, Object... args) {} } - static class FakeTrustManager implements X509TrustManager { - - @Override - public void checkClientTrusted(X509Certificate[] x509Certificates, String s) - throws CertificateException { - - } - - @Override - public void checkServerTrusted(X509Certificate[] x509Certificates, String s) - throws CertificateException { - - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - } - static class FakeClientTransportListener implements ManagedClientTransport.Listener { private boolean isConnected = false; @@ -1273,4 +1293,30 @@ public void transportInUse(boolean inUse) { } } + + private class FakeTrustManager implements X509TrustManager { + + private final X509TrustManager delegate; + + public FakeTrustManager(X509TrustManager x509ExtendedTrustManager) { + this.delegate = x509ExtendedTrustManager; + } + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) + throws CertificateException { + delegate.checkClientTrusted(x509Certificates, s); + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) + throws CertificateException { + delegate.checkServerTrusted(x509Certificates, s); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return delegate.getAcceptedIssuers(); + } + } } From dd95f8fbd94d9375060ba02e4adb1fb57f6803e6 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Mon, 2 Dec 2024 06:07:53 +0000 Subject: [PATCH 07/70] in-progress changes. --- .../io/grpc/okhttp/OkHttpClientTransport.java | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 3b861d23d28..a48d4f52ea3 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -47,6 +47,7 @@ import io.grpc.internal.ClientStream; import io.grpc.internal.ClientStreamListener.RpcProgress; import io.grpc.internal.ConnectionClientTransport; +import io.grpc.internal.FailingClientStream; import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; import io.grpc.internal.Http2Ping; @@ -71,12 +72,19 @@ import io.grpc.okhttp.internal.framed.Variant; import io.grpc.okhttp.internal.proxy.HttpUrl; import io.grpc.okhttp.internal.proxy.Request; +import io.grpc.util.CertificateUtils; import io.perfmark.PerfMark; +import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; +import java.io.InputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.net.URI; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Collections; import java.util.Deque; import java.util.EnumMap; @@ -104,7 +112,10 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; +import javax.security.auth.x500.X500Principal; import okio.Buffer; import okio.BufferedSink; import okio.BufferedSource; @@ -212,7 +223,7 @@ private static Map buildErrorCodeToStatusMap() { private final boolean useGetForSafeMethods; @GuardedBy("lock") private final TransportTracer transportTracer; - private Optional x509ExtendedTrustManager; + private Optional x509ExtendedTrustManager; @GuardedBy("lock") private final InUseStateAggregator inUseState = @@ -412,7 +423,19 @@ public ClientStream newStream( StatsTraceContext.newClientContext(tracers, getAttributes(), headers); if (callOptions.getAuthority() != null && channelCredentials instanceof TlsChannelCredentials) { if (x509ExtendedTrustManager == null) { - + try { + x509ExtendedTrustManager = getX509ExtendedTrustManager( + (TlsChannelCredentials) channelCredentials); + } catch (GeneralSecurityException e) { + log.log(Level.FINE, "Failure getting X509ExtendedTrustManager from TlsCredentials", e); + return new FailingClientStream(Status.INTERNAL.withDescription( + "Failure getting X509ExtendedTrustManager from TlsCredentials"), + tracers); + } + } else if (!x509ExtendedTrustManager.isPresent()) { + return new FailingClientStream(Status.INTERNAL.withDescription( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), + tracers); } } // FIXME: it is likely wrong to pass the transportTracer here as it'll exit the lock's scope @@ -435,6 +458,47 @@ public ClientStream newStream( } } + private Optional getX509ExtendedTrustManager(TlsChannelCredentials tlsCreds) + throws GeneralSecurityException { + Optional x509ExtendedTrustManager; + if (tlsCreds.getTrustManagers() != null) { + x509ExtendedTrustManager = tlsCreds.getTrustManagers().stream().filter( + trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + } else if (tlsCreds.getRootCertificates() != null) { + x509ExtendedTrustManager = getX509ExtendedTrustManager(new ByteArrayInputStream( + tlsCreds.getRootCertificates())); + } else { // else use system default + TrustManagerFactory tmf = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + x509ExtendedTrustManager = Arrays.stream(tmf.getTrustManagers()) + .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + } + return x509ExtendedTrustManager; + } + + private static Optional getX509ExtendedTrustManager(InputStream rootCerts) + throws GeneralSecurityException { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + try { + ks.load(null, null); + } catch (IOException ex) { + // Shouldn't really happen, as we're not loading any data. + throw new GeneralSecurityException(ex); + } + X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); + for (X509Certificate cert : certs) { + X500Principal principal = cert.getSubjectX500Principal(); + ks.setCertificateEntry(principal.getName("RFC2253"), cert); + } + + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(ks); + return Arrays.stream(trustManagerFactory.getTrustManagers()) + .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + } + @GuardedBy("lock") void streamReadyToStart(OkHttpClientStream clientStream) { if (goAwayStatus != null) { From 5ba5431e44d661385a11c6b8566258e63efd1462 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Mon, 2 Dec 2024 13:51:50 +0000 Subject: [PATCH 08/70] nit changes and drop unintended changes. --- .../helloworldtls/HelloWorldClientTls.java | 15 +++---- netty/build.gradle | 3 +- .../io/grpc/netty/ProtocolNegotiators.java | 44 +++++-------------- .../grpc/netty/NettyClientTransportTest.java | 12 ++--- 4 files changed, 21 insertions(+), 53 deletions(-) diff --git a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java index 2dd2646e623..9fa75cb2cb0 100644 --- a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java +++ b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java @@ -16,7 +16,6 @@ package io.grpc.examples.helloworldtls; -import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.Grpc; import io.grpc.ManagedChannel; @@ -53,9 +52,7 @@ public void greet(String name) { HelloRequest request = HelloRequest.newBuilder().setName(name).build(); HelloReply response; try { - // response = blockingStub.sayHello(request); - response = io.grpc.stub.ClientCalls.blockingUnaryCall( - blockingStub.getChannel(), GreeterGrpc.getSayHelloMethod(), CallOptions.DEFAULT.withAuthority("localhost"), request); + response = blockingStub.sayHello(request); } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); return; @@ -71,8 +68,8 @@ public static void main(String[] args) throws Exception { if (args.length < 2 || args.length == 4 || args.length > 5) { System.out.println("USAGE: HelloWorldClientTls host port [trustCertCollectionFilePath " + - "[clientCertChainFilePath clientPrivateKeyFilePath]]\n Note: clientCertChainFilePath and " + - "clientPrivateKeyFilePath are only needed if mutual auth is desired."); + "[clientCertChainFilePath clientPrivateKeyFilePath]]\n Note: clientCertChainFilePath and " + + "clientPrivateKeyFilePath are only needed if mutual auth is desired."); System.exit(0); } @@ -91,9 +88,9 @@ public static void main(String[] args) throws Exception { String host = args[0]; int port = Integer.parseInt(args[1]); ManagedChannel channel = Grpc.newChannelBuilderForAddress(host, port, tlsBuilder.build()) - /* Only for using provided test certs. */ - .overrideAuthority("foo.test.google.fr") - .build(); + /* Only for using provided test certs. */ + .overrideAuthority("foo.test.google.fr") + .build(); try { HelloWorldClientTls client = new HelloWorldClientTls(channel); client.greet(host); diff --git a/netty/build.gradle b/netty/build.gradle index 1a859a03bd6..8e6cd8c7f0e 100644 --- a/netty/build.gradle +++ b/netty/build.gradle @@ -17,8 +17,7 @@ tasks.named("jar").configure { dependencies { api project(':grpc-api'), - libraries.netty.codec.http2 - implementation project(':grpc-util'), + libraries.netty.codec.http2, project(':grpc-core'), libs.netty.handler.proxy, libraries.guava, diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index dbaa9b5fd95..4275ddfb007 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -1258,9 +1258,7 @@ public boolean isInboundDone() { } @Override - public void closeOutbound() { - - } + public void closeOutbound() {} @Override public boolean isOutboundDone() { @@ -1278,9 +1276,7 @@ public String[] getEnabledCipherSuites() { } @Override - public void setEnabledCipherSuites(String[] strings) { - - } + public void setEnabledCipherSuites(String[] strings) {} @Override public String[] getSupportedProtocols() { @@ -1293,9 +1289,7 @@ public String[] getEnabledProtocols() { } @Override - public void setEnabledProtocols(String[] strings) { - - } + public void setEnabledProtocols(String[] strings) {} @Override public SSLSession getSession() { @@ -1303,9 +1297,7 @@ public SSLSession getSession() { } @Override - public void beginHandshake() throws SSLException { - - } + public void beginHandshake() throws SSLException {} @Override public HandshakeStatus getHandshakeStatus() { @@ -1313,9 +1305,7 @@ public HandshakeStatus getHandshakeStatus() { } @Override - public void setUseClientMode(boolean b) { - - } + public void setUseClientMode(boolean b) {} @Override public boolean getUseClientMode() { @@ -1323,9 +1313,7 @@ public boolean getUseClientMode() { } @Override - public void setNeedClientAuth(boolean b) { - - } + public void setNeedClientAuth(boolean b) {} @Override public boolean getNeedClientAuth() { @@ -1333,9 +1321,7 @@ public boolean getNeedClientAuth() { } @Override - public void setWantClientAuth(boolean b) { - - } + public void setWantClientAuth(boolean b) {} @Override public boolean getWantClientAuth() { @@ -1343,9 +1329,7 @@ public boolean getWantClientAuth() { } @Override - public void setEnableSessionCreation(boolean b) { - - } + public void setEnableSessionCreation(boolean b) {} @Override public boolean getEnableSessionCreation() { @@ -1387,9 +1371,7 @@ public long getLastAccessedTime() { } @Override - public void invalidate() { - - } + public void invalidate() {} @Override public boolean isValid() { @@ -1397,9 +1379,7 @@ public boolean isValid() { } @Override - public void putValue(String s, Object o) { - - } + public void putValue(String s, Object o) {} @Override public Object getValue(String s) { @@ -1407,9 +1387,7 @@ public Object getValue(String s) { } @Override - public void removeValue(String s) { - - } + public void removeValue(String s) {} @Override public String[] getValueNames() { diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index aeb1a835ebb..04666cf978a 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -1271,14 +1271,10 @@ static class FakeClientTransportListener implements ManagedClientTransport.Liste private boolean isConnected = false; @Override - public void transportShutdown(Status s) { - - } + public void transportShutdown(Status s) {} @Override - public void transportTerminated() { - - } + public void transportTerminated() {} @Override public void transportReady() { @@ -1289,9 +1285,7 @@ public void transportReady() { } @Override - public void transportInUse(boolean inUse) { - - } + public void transportInUse(boolean inUse) {} } private class FakeTrustManager implements X509TrustManager { From e42492b00f18bda52d4b1a2eadadca06631e9cef Mon Sep 17 00:00:00 2001 From: Kannan J Date: Mon, 2 Dec 2024 13:54:38 +0000 Subject: [PATCH 09/70] nit changes and drop unintended changes. --- netty/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netty/build.gradle b/netty/build.gradle index 8e6cd8c7f0e..1a859a03bd6 100644 --- a/netty/build.gradle +++ b/netty/build.gradle @@ -17,7 +17,8 @@ tasks.named("jar").configure { dependencies { api project(':grpc-api'), - libraries.netty.codec.http2, + libraries.netty.codec.http2 + implementation project(':grpc-util'), project(':grpc-core'), libs.netty.handler.proxy, libraries.guava, From 5285353802c6702a9867e9f8fbdc02627d0500d1 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 4 Dec 2024 06:09:03 +0000 Subject: [PATCH 10/70] Cache the peer verification result. --- .../io/grpc/netty/NettyClientTransport.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index 7a9eff5c917..869a66a218e 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -63,6 +63,7 @@ import java.nio.channels.ClosedChannelException; import java.security.cert.CertificateException; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -111,6 +112,7 @@ class NettyClientTransport implements ConnectionClientTransport { private final ChannelLogger channelLogger; private final boolean useGetForSafeMethods; private final Ticker ticker; + private final ConcurrentHashMap authoritiesAllowedForPeer = new ConcurrentHashMap<>(); NettyClientTransport( SocketAddress address, @@ -208,11 +210,21 @@ public ClientStream newStream( "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), tracers); } - try { - clientTlsProtocolNegotiator.verifyAuthorityAllowedForPeerCert(callOptions.getAuthority()); - } catch (SSLPeerUnverifiedException | CertificateException e) { - logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.", - callOptions.getAuthority()); + boolean peerVerified; + if (authoritiesAllowedForPeer.containsKey(callOptions.getAuthority())) { + peerVerified = authoritiesAllowedForPeer.get(callOptions.getAuthority()); + } else { + try { + clientTlsProtocolNegotiator.verifyAuthorityAllowedForPeerCert(callOptions.getAuthority()); + peerVerified = true; + } catch (SSLPeerUnverifiedException | CertificateException e) { + peerVerified = false; + logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.", + callOptions.getAuthority()); + } + authoritiesAllowedForPeer.put(callOptions.getAuthority(), peerVerified); + } + if (!peerVerified) { return new FailingClientStream(Status.INTERNAL.withDescription( "Peer hostname verification failed for authority"), tracers); } From ea969ef55bc40715dc7567c4e3062cd93c76682b Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 4 Dec 2024 06:21:16 +0000 Subject: [PATCH 11/70] Move extraction of X509ExtendedTrustManager to utils. --- .../io/grpc/netty/ProtocolNegotiators.java | 23 +------------ .../java/io/grpc/util/CertificateUtils.java | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 4275ddfb007..9b1e09ef8cb 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static io.grpc.util.CertificateUtils.getX509ExtendedTrustManager; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; @@ -271,28 +272,6 @@ public static FromServerCredentialsResult from(ServerCredentials creds) { } } - private static Optional getX509ExtendedTrustManager(InputStream rootCerts) - throws GeneralSecurityException { - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - try { - ks.load(null, null); - } catch (IOException ex) { - // Shouldn't really happen, as we're not loading any data. - throw new GeneralSecurityException(ex); - } - X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); - for (X509Certificate cert : certs) { - X500Principal principal = cert.getSubjectX500Principal(); - ks.setCertificateEntry(principal.getName("RFC2253"), cert); - } - - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(ks); - return Arrays.stream(trustManagerFactory.getTrustManagers()) - .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); - } - public static final class FromChannelCredentialsResult { public final ProtocolNegotiator.ClientFactory negotiator; public final CallCredentials callCredentials; diff --git a/util/src/main/java/io/grpc/util/CertificateUtils.java b/util/src/main/java/io/grpc/util/CertificateUtils.java index e7082f177ab..400c6a54a5a 100644 --- a/util/src/main/java/io/grpc/util/CertificateUtils.java +++ b/util/src/main/java/io/grpc/util/CertificateUtils.java @@ -23,7 +23,9 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; import java.security.KeyFactory; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; @@ -32,7 +34,13 @@ import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Arrays; import java.util.Collection; +import java.util.Optional; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; +import javax.security.auth.x500.X500Principal; /** * Contains certificate/key PEM file utility method(s). @@ -91,5 +99,31 @@ public static PrivateKey getPrivateKey(InputStream inputStream) } } } + + /** + * Creates a X509ExtendedTrustManager using the provided CA certs if applicable for the + * certificate type. + */ + public static Optional getX509ExtendedTrustManager(InputStream rootCerts) + throws GeneralSecurityException { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + try { + ks.load(null, null); + } catch (IOException ex) { + // Shouldn't really happen, as we're not loading any data. + throw new GeneralSecurityException(ex); + } + X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); + for (X509Certificate cert : certs) { + X500Principal principal = cert.getSubjectX500Principal(); + ks.setCertificateEntry(principal.getName("RFC2253"), cert); + } + + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(ks); + return Arrays.stream(trustManagerFactory.getTrustManagers()) + .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + } } From 4d71cceb7138011efffd57ebedec8d6f42917dd7 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 4 Dec 2024 07:57:35 +0000 Subject: [PATCH 12/70] commit --- .../internal/ManagedChannelImplBuilder.java | 4 - .../io/grpc/okhttp/OkHttpChannelBuilder.java | 4 +- .../io/grpc/okhttp/OkHttpClientTransport.java | 300 ++++++++++++++++-- .../java/io/grpc/util/CertificateUtils.java | 34 ++ 4 files changed, 305 insertions(+), 37 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java b/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java index 180d80d0d44..48a255472e1 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java @@ -360,10 +360,6 @@ public ManagedChannelImplBuilder(SocketAddress directServerAddress, String autho InternalConfiguratorRegistry.configureChannelBuilder(this); } - public ChannelCredentials getChannelCredentials() { - return channelCredentials; - } - @Override public ManagedChannelImplBuilder directExecutor() { return executor(MoreExecutors.directExecutor()); diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java index 15c84708474..8a08567a9c9 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java @@ -92,6 +92,7 @@ public final class OkHttpChannelBuilder extends ForwardingChannelBuilder2 ERROR_CODE_TO_STATUS = buildErrorCodeToStatusMap(); private static final Logger log = Logger.getLogger(OkHttpClientTransport.class.getName()); private final ChannelCredentials channelCredentials; + private Socket sock; + private SSLSession sslSession; private static Map buildErrorCodeToStatusMap() { Map errorToStatus = new EnumMap<>(ErrorCode.class); @@ -223,7 +232,7 @@ private static Map buildErrorCodeToStatusMap() { private final boolean useGetForSafeMethods; @GuardedBy("lock") private final TransportTracer transportTracer; - private Optional x509ExtendedTrustManager; + private final ConcurrentHashMap authoritiesAllowedForPeer = new ConcurrentHashMap<>(); @GuardedBy("lock") private final InUseStateAggregator inUseState = @@ -421,9 +430,9 @@ public ClientStream newStream( Preconditions.checkNotNull(headers, "headers"); StatsTraceContext statsTraceContext = StatsTraceContext.newClientContext(tracers, getAttributes(), headers); - if (callOptions.getAuthority() != null && channelCredentials instanceof TlsChannelCredentials) { - if (x509ExtendedTrustManager == null) { - try { + if (socket instanceof SSLSocket && callOptions.getAuthority() != null && channelCredentials != null && channelCredentials instanceof TlsChannelCredentials) { + Optional x509ExtendedTrustManager; + try { x509ExtendedTrustManager = getX509ExtendedTrustManager( (TlsChannelCredentials) channelCredentials); } catch (GeneralSecurityException e) { @@ -432,11 +441,24 @@ public ClientStream newStream( "Failure getting X509ExtendedTrustManager from TlsCredentials"), tracers); } - } else if (!x509ExtendedTrustManager.isPresent()) { + if (!x509ExtendedTrustManager.isPresent()) { return new FailingClientStream(Status.INTERNAL.withDescription( "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), tracers); } + try { + Certificate[] peerCertificates = sslSession.getPeerCertificates(); + X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; + for (int i = 0; i < peerCertificates.length; i++) { + x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; + } + ((X509ExtendedTrustManager) x509ExtendedTrustManager.get()).checkServerTrusted(x509PeerCertificates, "RSA", new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); + } catch (SSLPeerUnverifiedException | CertificateException e) { + log.log(Level.FINE, "Failure in verifying authority against peer", e); + return new FailingClientStream(Status.INTERNAL.withDescription( + "Failure in verifying authority against peer"), + tracers); + } } // FIXME: it is likely wrong to pass the transportTracer here as it'll exit the lock's scope synchronized (lock) { // to make @GuardedBy linter happy @@ -465,7 +487,7 @@ private Optional getX509ExtendedTrustManager(TlsChannelCredentials x509ExtendedTrustManager = tlsCreds.getTrustManagers().stream().filter( trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); } else if (tlsCreds.getRootCertificates() != null) { - x509ExtendedTrustManager = getX509ExtendedTrustManager(new ByteArrayInputStream( + x509ExtendedTrustManager = CertificateUtils.getX509ExtendedTrustManager(new ByteArrayInputStream( tlsCreds.getRootCertificates())); } else { // else use system default TrustManagerFactory tmf = TrustManagerFactory.getInstance( @@ -477,28 +499,6 @@ private Optional getX509ExtendedTrustManager(TlsChannelCredentials return x509ExtendedTrustManager; } - private static Optional getX509ExtendedTrustManager(InputStream rootCerts) - throws GeneralSecurityException { - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - try { - ks.load(null, null); - } catch (IOException ex) { - // Shouldn't really happen, as we're not loading any data. - throw new GeneralSecurityException(ex); - } - X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); - for (X509Certificate cert : certs) { - X500Principal principal = cert.getSubjectX500Principal(); - ks.setCertificateEntry(principal.getName("RFC2253"), cert); - } - - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(ks); - return Arrays.stream(trustManagerFactory.getTrustManagers()) - .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); - } - @GuardedBy("lock") void streamReadyToStart(OkHttpClientStream clientStream) { if (goAwayStatus != null) { @@ -614,8 +614,6 @@ public Timeout timeout() { public void close() { } }); - Socket sock; - SSLSession sslSession = null; try { // This is a hack to make sure the connection preface and initial settings to be sent out // without blocking the start. By doing this essentially prevents potential deadlock when @@ -1543,4 +1541,242 @@ public void alternateService(int streamId, String origin, ByteString protocol, S // TODO(madongfly): Deal with alternateService propagation } } + + /** + * SSLSocket wrapper that provides a fake SSLSession for handshake session. + */ + static class SslSocketWrapper extends SSLSocket { + + private final SSLSession sslSession; + private final SSLSocket sslSocket; + + SslSocketWrapper(SSLSocket sslSocket, String peerHost) { + this.sslSocket = sslSocket; + this.sslSession = new FakeSslSession(peerHost); + } + + @Override + public SSLSession getHandshakeSession() { + return this.sslSession; + } + + @Override + public boolean isConnected() { + return sslSocket.isConnected(); + } + + @Override + public String[] getSupportedCipherSuites() { + return new String[0]; + } + + @Override + public String[] getEnabledCipherSuites() { + return new String[0]; + } + + @Override + public void setEnabledCipherSuites(String[] strings) { + + } + + @Override + public String[] getSupportedProtocols() { + return new String[0]; + } + + @Override + public String[] getEnabledProtocols() { + return new String[0]; + } + + @Override + public void setEnabledProtocols(String[] strings) { + + } + + @Override + public SSLSession getSession() { + return null; + } + + @Override + public void addHandshakeCompletedListener( + HandshakeCompletedListener handshakeCompletedListener) { + + } + + @Override + public void removeHandshakeCompletedListener( + HandshakeCompletedListener handshakeCompletedListener) { + + } + + @Override + public void startHandshake() throws IOException { + + } + + @Override + public void setUseClientMode(boolean b) { + + } + + @Override + public boolean getUseClientMode() { + return false; + } + + @Override + public void setNeedClientAuth(boolean b) { + + } + + @Override + public boolean getNeedClientAuth() { + return false; + } + + @Override + public void setWantClientAuth(boolean b) { + + } + + @Override + public boolean getWantClientAuth() { + return false; + } + + @Override + public void setEnableSessionCreation(boolean b) { + + } + + @Override + public boolean getEnableSessionCreation() { + return false; + } + } + + /** + * Fake SSLSession instance that provides the peer host name to verify for per-rpc check. + */ + static class FakeSslSession extends ExtendedSSLSession { + + private final String peerHost; + + FakeSslSession(String peerHost) { + this.peerHost = peerHost; + } + + @Override + public String getPeerHost() { + return peerHost; + } + + @Override + public byte[] getId() { + return new byte[0]; + } + + @Override + public SSLSessionContext getSessionContext() { + return null; + } + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public long getLastAccessedTime() { + return 0; + } + + @Override + public void invalidate() { + + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public void putValue(String s, Object o) { + + } + + @Override + public Object getValue(String s) { + return null; + } + + @Override + public void removeValue(String s) { + + } + + @Override + public String[] getValueNames() { + return new String[0]; + } + + @Override + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { + return new Certificate[0]; + } + + @Override + public Certificate[] getLocalCertificates() { + return new Certificate[0]; + } + + @Override + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + return null; + } + + @Override + public Principal getLocalPrincipal() { + return null; + } + + @Override + public String getCipherSuite() { + return null; + } + + @Override + public String getProtocol() { + return null; + } + + @Override + public int getPeerPort() { + return 0; + } + + @Override + public int getPacketBufferSize() { + return 0; + } + + @Override + public int getApplicationBufferSize() { + return 0; + } + + @Override + public String[] getLocalSupportedSignatureAlgorithms() { + return new String[0]; + } + + @Override + public String[] getPeerSupportedSignatureAlgorithms() { + return new String[0]; + } + } } \ No newline at end of file diff --git a/util/src/main/java/io/grpc/util/CertificateUtils.java b/util/src/main/java/io/grpc/util/CertificateUtils.java index e7082f177ab..400c6a54a5a 100644 --- a/util/src/main/java/io/grpc/util/CertificateUtils.java +++ b/util/src/main/java/io/grpc/util/CertificateUtils.java @@ -23,7 +23,9 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; import java.security.KeyFactory; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; @@ -32,7 +34,13 @@ import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Arrays; import java.util.Collection; +import java.util.Optional; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; +import javax.security.auth.x500.X500Principal; /** * Contains certificate/key PEM file utility method(s). @@ -91,5 +99,31 @@ public static PrivateKey getPrivateKey(InputStream inputStream) } } } + + /** + * Creates a X509ExtendedTrustManager using the provided CA certs if applicable for the + * certificate type. + */ + public static Optional getX509ExtendedTrustManager(InputStream rootCerts) + throws GeneralSecurityException { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + try { + ks.load(null, null); + } catch (IOException ex) { + // Shouldn't really happen, as we're not loading any data. + throw new GeneralSecurityException(ex); + } + X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); + for (X509Certificate cert : certs) { + X500Principal principal = cert.getSubjectX500Principal(); + ks.setCertificateEntry(principal.getName("RFC2253"), cert); + } + + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(ks); + return Arrays.stream(trustManagerFactory.getTrustManagers()) + .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + } } From 94172de346de1b632d07dbab8c4fa83552067435 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 4 Dec 2024 09:09:12 +0000 Subject: [PATCH 13/70] Fixes. --- netty/src/main/java/io/grpc/netty/NettyClientTransport.java | 3 ++- netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java | 4 ---- .../src/test/java/io/grpc/netty/NettyClientTransportTest.java | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index 869a66a218e..6415d701cf2 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -112,7 +112,8 @@ class NettyClientTransport implements ConnectionClientTransport { private final ChannelLogger channelLogger; private final boolean useGetForSafeMethods; private final Ticker ticker; - private final ConcurrentHashMap authoritiesAllowedForPeer = new ConcurrentHashMap<>(); + private final ConcurrentHashMap authoritiesAllowedForPeer = + new ConcurrentHashMap<>(); NettyClientTransport( SocketAddress address, diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 9b1e09ef8cb..4a6486680b4 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -45,7 +45,6 @@ import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; -import io.grpc.util.CertificateUtils; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; @@ -69,8 +68,6 @@ import io.netty.handler.ssl.SslProvider; import io.netty.util.AsciiString; import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; import java.net.SocketAddress; import java.net.URI; import java.nio.ByteBuffer; @@ -100,7 +97,6 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; -import javax.security.auth.x500.X500Principal; /** * Common {@link ProtocolNegotiator}s used by gRPC. diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 04666cf978a..60e95466a5b 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -1288,7 +1288,7 @@ public void transportReady() { public void transportInUse(boolean inUse) {} } - private class FakeTrustManager implements X509TrustManager { + private static class FakeTrustManager implements X509TrustManager { private final X509TrustManager delegate; From 0ddd42c55df32689b5a6e7c5092e0afda855b975 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 4 Dec 2024 09:34:41 +0000 Subject: [PATCH 14/70] Fixes. --- netty/BUILD.bazel | 1 + netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java | 1 + 2 files changed, 2 insertions(+) diff --git a/netty/BUILD.bazel b/netty/BUILD.bazel index 9fe52ea5868..9521c60d01d 100644 --- a/netty/BUILD.bazel +++ b/netty/BUILD.bazel @@ -12,6 +12,7 @@ java_library( deps = [ "//api", "//core:internal", + "//util", artifact("com.google.code.findbugs:jsr305"), artifact("com.google.errorprone:error_prone_annotations"), artifact("com.google.guava:guava"), diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 4a6486680b4..37170dfcce5 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -1330,6 +1330,7 @@ public SSLSessionContext getSessionContext() { } @Override + @SuppressWarnings("deprecation") public javax.security.cert.X509Certificate[] getPeerCertificateChain() { throw new UnsupportedOperationException("This method is deprecated and marked for removal. " + "Use the getPeerCertificates() method instead."); From 909b8634d0daa7a3ba870b4c7266d5d20a1d1921 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 4 Dec 2024 10:10:46 +0000 Subject: [PATCH 15/70] Fixes. --- .../io/grpc/okhttp/OkHttpClientTransport.java | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 7640ae5845b..1a353abccc7 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -129,7 +129,6 @@ import okio.Okio; import okio.Source; import okio.Timeout; -import sun.security.ssl.SSLSocketImpl; /** * A okhttp-based {@link ConnectionClientTransport} implementation. @@ -431,8 +430,12 @@ public ClientStream newStream( StatsTraceContext statsTraceContext = StatsTraceContext.newClientContext(tracers, getAttributes(), headers); if (socket instanceof SSLSocket && callOptions.getAuthority() != null && channelCredentials != null && channelCredentials instanceof TlsChannelCredentials) { - Optional x509ExtendedTrustManager; - try { + boolean isAuthorityValid; + if (authoritiesAllowedForPeer.containsKey(callOptions.getAuthority())) { + isAuthorityValid = authoritiesAllowedForPeer.get(callOptions.getAuthority()); + } else { + Optional x509ExtendedTrustManager; + try { x509ExtendedTrustManager = getX509ExtendedTrustManager( (TlsChannelCredentials) channelCredentials); } catch (GeneralSecurityException e) { @@ -441,23 +444,28 @@ public ClientStream newStream( "Failure getting X509ExtendedTrustManager from TlsCredentials"), tracers); } - if (!x509ExtendedTrustManager.isPresent()) { - return new FailingClientStream(Status.INTERNAL.withDescription( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), - tracers); - } - try { - Certificate[] peerCertificates = sslSession.getPeerCertificates(); - X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; - for (int i = 0; i < peerCertificates.length; i++) { - x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; + if (!x509ExtendedTrustManager.isPresent()) { + return new FailingClientStream(Status.INTERNAL.withDescription( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), + tracers); + } + try { + Certificate[] peerCertificates = sslSession.getPeerCertificates(); + X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; + for (int i = 0; i < peerCertificates.length; i++) { + x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; + } + ((X509ExtendedTrustManager) x509ExtendedTrustManager.get()).checkServerTrusted( + x509PeerCertificates, "RSA", + new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); + authoritiesAllowedForPeer.put(callOptions.getAuthority(), true); + } catch (SSLPeerUnverifiedException | CertificateException e) { + log.log(Level.FINE, "Failure in verifying authority against peer", e); + authoritiesAllowedForPeer.put(callOptions.getAuthority(), false); + return new FailingClientStream(Status.INTERNAL.withDescription( + "Failure in verifying authority against peer"), + tracers); } - ((X509ExtendedTrustManager) x509ExtendedTrustManager.get()).checkServerTrusted(x509PeerCertificates, "RSA", new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); - } catch (SSLPeerUnverifiedException | CertificateException e) { - log.log(Level.FINE, "Failure in verifying authority against peer", e); - return new FailingClientStream(Status.INTERNAL.withDescription( - "Failure in verifying authority against peer"), - tracers); } } // FIXME: it is likely wrong to pass the transportTracer here as it'll exit the lock's scope @@ -1674,6 +1682,11 @@ public String getPeerHost() { return peerHost; } + @SuppressWarnings("deprecation") + public javax.security.cert.X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { + throw new UnsupportedOperationException("This method is deprecated and marked for removal. Use the getPeerCertificates() method instead."); + } + @Override public byte[] getId() { return new byte[0]; From 5109115b7331d04f1eb876c356658fb16db50dde Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 4 Dec 2024 10:16:04 +0000 Subject: [PATCH 16/70] Fixes. --- examples/example-tls/build.gradle | 4 +++- .../examples/helloworldtls/HelloWorldClientTls.java | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index f46b2919a5f..9248749ea5e 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -75,6 +75,8 @@ application { applicationDistribution.into('bin') { from(helloWorldTlsServer) from(helloWorldTlsClient) - fileMode = 0755 + filePermissions { + unix(0755) + } } } diff --git a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java index 9fa75cb2cb0..6239318de21 100644 --- a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java +++ b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java @@ -68,8 +68,8 @@ public static void main(String[] args) throws Exception { if (args.length < 2 || args.length == 4 || args.length > 5) { System.out.println("USAGE: HelloWorldClientTls host port [trustCertCollectionFilePath " + - "[clientCertChainFilePath clientPrivateKeyFilePath]]\n Note: clientCertChainFilePath and " + - "clientPrivateKeyFilePath are only needed if mutual auth is desired."); + "[clientCertChainFilePath clientPrivateKeyFilePath]]\n Note: clientCertChainFilePath and " + + "clientPrivateKeyFilePath are only needed if mutual auth is desired."); System.exit(0); } @@ -88,9 +88,9 @@ public static void main(String[] args) throws Exception { String host = args[0]; int port = Integer.parseInt(args[1]); ManagedChannel channel = Grpc.newChannelBuilderForAddress(host, port, tlsBuilder.build()) - /* Only for using provided test certs. */ - .overrideAuthority("foo.test.google.fr") - .build(); + /* Only for using provided test certs. */ + .overrideAuthority("foo.test.google.fr") + .build(); try { HelloWorldClientTls client = new HelloWorldClientTls(channel); client.greet(host); From d5f3968944601706782fb068ea4c50882a9c0ed7 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 4 Dec 2024 10:17:52 +0000 Subject: [PATCH 17/70] nit --- .../io/grpc/examples/helloworldtls/HelloWorldClientTls.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java index 6239318de21..4ff7e23a299 100644 --- a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java +++ b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java @@ -68,8 +68,8 @@ public static void main(String[] args) throws Exception { if (args.length < 2 || args.length == 4 || args.length > 5) { System.out.println("USAGE: HelloWorldClientTls host port [trustCertCollectionFilePath " + - "[clientCertChainFilePath clientPrivateKeyFilePath]]\n Note: clientCertChainFilePath and " + - "clientPrivateKeyFilePath are only needed if mutual auth is desired."); + "[clientCertChainFilePath clientPrivateKeyFilePath]]\n Note: clientCertChainFilePath and " + + "clientPrivateKeyFilePath are only needed if mutual auth is desired."); System.exit(0); } From 24500f45bf8bef009da4b53d3f963906bc1cf56a Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 4 Dec 2024 14:02:41 +0000 Subject: [PATCH 18/70] Changes. --- .../java/io/grpc/ManagedChannelRegistry.java | 30 ++-- examples/example-tls/build.gradle | 6 +- .../helloworldtls/HelloWorldClientTls.java | 7 +- .../io/grpc/okhttp/OkHttpChannelBuilder.java | 8 +- .../io/grpc/okhttp/OkHttpClientTransport.java | 39 ++--- .../okhttp/OkHttpClientTransportTest.java | 152 ++++++++++-------- 6 files changed, 130 insertions(+), 112 deletions(-) diff --git a/api/src/main/java/io/grpc/ManagedChannelRegistry.java b/api/src/main/java/io/grpc/ManagedChannelRegistry.java index 31f874b8094..d2ed9e496ae 100644 --- a/api/src/main/java/io/grpc/ManagedChannelRegistry.java +++ b/api/src/main/java/io/grpc/ManagedChannelRegistry.java @@ -181,21 +181,21 @@ ManagedChannelBuilder newChannelBuilder(NameResolverRegistry nameResolverRegi + "artifact"); } StringBuilder error = new StringBuilder(); - for (ManagedChannelProvider provider : providers()) { - Collection> channelProviderSocketAddressTypes - = provider.getSupportedSocketAddressTypes(); - if (!channelProviderSocketAddressTypes.containsAll(nameResolverSocketAddressTypes)) { - error.append("; "); - error.append(provider.getClass().getName()); - error.append(": does not support 1 or more of "); - error.append(Arrays.toString(nameResolverSocketAddressTypes.toArray())); - continue; - } - ManagedChannelProvider.NewChannelBuilderResult result - = provider.newChannelBuilder(target, creds); - if (result.getChannelBuilder() != null) { - return result.getChannelBuilder(); - } + for (ManagedChannelProvider provider : providers()) { + Collection> channelProviderSocketAddressTypes + = provider.getSupportedSocketAddressTypes(); + if (!channelProviderSocketAddressTypes.containsAll(nameResolverSocketAddressTypes)) { + error.append("; "); + error.append(provider.getClass().getName()); + error.append(": does not support 1 or more of "); + error.append(Arrays.toString(nameResolverSocketAddressTypes.toArray())); + continue; + } + ManagedChannelProvider.NewChannelBuilderResult result + = provider.newChannelBuilder(target, creds); + if (result.getChannelBuilder() != null) { + return result.getChannelBuilder(); + } error.append("; "); error.append(provider.getClass().getName()); error.append(": "); diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 1eb51182309..7c7deb3b7d1 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -28,6 +28,8 @@ def grpcVersion = '1.69.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.5' dependencies { + implementation "io.grpc:grpc-api:${grpcVersion}" + implementation "io.grpc:grpc-okhttp:${grpcVersion}" implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" compileOnly "org.apache.tomcat:annotations-api:6.0.53" @@ -74,8 +76,6 @@ application { applicationDistribution.into('bin') { from(helloWorldTlsServer) from(helloWorldTlsClient) - filePermissions { - unix(0755) - } + fileMode = 0755 } } diff --git a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java index 4ff7e23a299..892d873c19c 100644 --- a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java +++ b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java @@ -18,6 +18,7 @@ import io.grpc.Channel; import io.grpc.Grpc; +import io.grpc.okhttp.OkHttpChannelBuilder; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; import io.grpc.TlsChannelCredentials; @@ -52,7 +53,9 @@ public void greet(String name) { HelloRequest request = HelloRequest.newBuilder().setName(name).build(); HelloReply response; try { - response = blockingStub.sayHello(request); + response = io.grpc.stub.ClientCalls.blockingUnaryCall( + blockingStub.getChannel(), GreeterGrpc.getSayHelloMethod(), + blockingStub.getCallOptions().withAuthority("foo.test.google.in"), request); } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); return; @@ -87,7 +90,7 @@ public static void main(String[] args) throws Exception { } String host = args[0]; int port = Integer.parseInt(args[1]); - ManagedChannel channel = Grpc.newChannelBuilderForAddress(host, port, tlsBuilder.build()) + ManagedChannel channel = OkHttpChannelBuilder.forAddress(host, port, tlsBuilder.build()) /* Only for using provided test certs. */ .overrideAuthority("foo.test.google.fr") .build(); diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java index 8a08567a9c9..fff5e47ff77 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java @@ -92,7 +92,7 @@ public final class OkHttpChannelBuilder extends ForwardingChannelBuilder2 buildErrorCodeToStatusMap() { private final boolean useGetForSafeMethods; @GuardedBy("lock") private final TransportTracer transportTracer; - private final ConcurrentHashMap authoritiesAllowedForPeer = new ConcurrentHashMap<>(); + private final ConcurrentHashMap authoritiesAllowedForPeer = + new ConcurrentHashMap<>(); @GuardedBy("lock") private final InUseStateAggregator inUseState = @@ -429,7 +431,8 @@ public ClientStream newStream( Preconditions.checkNotNull(headers, "headers"); StatsTraceContext statsTraceContext = StatsTraceContext.newClientContext(tracers, getAttributes(), headers); - if (socket instanceof SSLSocket && callOptions.getAuthority() != null && channelCredentials != null && channelCredentials instanceof TlsChannelCredentials) { + if (socket instanceof SSLSocket && callOptions.getAuthority() != null + && channelCredentials != null && channelCredentials instanceof TlsChannelCredentials) { boolean isAuthorityValid; if (authoritiesAllowedForPeer.containsKey(callOptions.getAuthority())) { isAuthorityValid = authoritiesAllowedForPeer.get(callOptions.getAuthority()); @@ -446,8 +449,8 @@ public ClientStream newStream( } if (!x509ExtendedTrustManager.isPresent()) { return new FailingClientStream(Status.INTERNAL.withDescription( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), - tracers); + "Can't allow authority override in rpc when X509ExtendedTrustManager is not " + + "available"), tracers); } try { Certificate[] peerCertificates = sslSession.getPeerCertificates(); @@ -495,8 +498,8 @@ private Optional getX509ExtendedTrustManager(TlsChannelCredentials x509ExtendedTrustManager = tlsCreds.getTrustManagers().stream().filter( trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); } else if (tlsCreds.getRootCertificates() != null) { - x509ExtendedTrustManager = CertificateUtils.getX509ExtendedTrustManager(new ByteArrayInputStream( - tlsCreds.getRootCertificates())); + x509ExtendedTrustManager = CertificateUtils.getX509ExtendedTrustManager( + new ByteArrayInputStream(tlsCreds.getRootCertificates())); } else { // else use system default TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); @@ -1573,6 +1576,13 @@ public boolean isConnected() { return sslSocket.isConnected(); } + @Override + public SSLParameters getSSLParameters() { + SSLParameters sslParameters = sslSocket.getSSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); + return sslParameters; + } + @Override public String[] getSupportedCipherSuites() { return new String[0]; @@ -1669,7 +1679,7 @@ public boolean getEnableSessionCreation() { /** * Fake SSLSession instance that provides the peer host name to verify for per-rpc check. */ - static class FakeSslSession extends ExtendedSSLSession { + static class FakeSslSession implements SSLSession { private final String peerHost; @@ -1683,8 +1693,9 @@ public String getPeerHost() { } @SuppressWarnings("deprecation") - public javax.security.cert.X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { - throw new UnsupportedOperationException("This method is deprecated and marked for removal. Use the getPeerCertificates() method instead."); + public javax.security.cert.X509Certificate[] getPeerCertificateChain() { + throw new UnsupportedOperationException("This method is deprecated and marked for removal. " + + "Use the getPeerCertificates() method instead."); } @Override @@ -1781,15 +1792,5 @@ public int getPacketBufferSize() { public int getApplicationBufferSize() { return 0; } - - @Override - public String[] getLocalSupportedSignatureAlgorithms() { - return new String[0]; - } - - @Override - public String[] getPeerSupportedSignatureAlgorithms() { - return new String[0]; - } } } \ No newline at end of file diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java index 0148551febe..54f2e108e15 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java @@ -69,6 +69,7 @@ import io.grpc.Status.Code; import io.grpc.StatusException; import io.grpc.internal.AbstractStream; +import io.grpc.internal.ClientStream; import io.grpc.internal.ClientStreamListener; import io.grpc.internal.ClientTransport; import io.grpc.internal.FakeClock; @@ -241,7 +242,8 @@ public void testToString() throws Exception { /*userAgent=*/ null, EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable, channelCredentials); + tooManyPingsRunnable, + null); String s = clientTransport.toString(); assertTrue("Unexpected: " + s, s.contains("OkHttpClientTransport")); assertTrue("Unexpected: " + s, s.contains(address.toString())); @@ -259,7 +261,8 @@ public void testTransportExecutorWithTooFewThreads() throws Exception { null, EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable, channelCredentials); + tooManyPingsRunnable, + null); clientTransport.start(transportListener); ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); verify(transportListener, timeout(TIME_OUT_MS)).transportShutdown(statusCaptor.capture()); @@ -300,7 +303,7 @@ public void close() throws SecurityException { assertThat(log.getLevel()).isEqualTo(Level.FINE); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -390,7 +393,7 @@ public void maxMessageSizeShouldBeEnforced() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -443,11 +446,11 @@ public void nextFrameThrowIoException() throws Exception { initTransport(); MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); - OkHttpClientStream stream1 = + ClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); stream1.request(1); - OkHttpClientStream stream2 = + ClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); stream2.request(1); @@ -477,7 +480,7 @@ public void nextFrameThrowIoException() throws Exception { public void nextFrameThrowsError() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -498,7 +501,7 @@ public void nextFrameThrowsError() throws Exception { public void nextFrameReturnFalse() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -516,7 +519,7 @@ public void readMessages() throws Exception { final int numMessages = 10; final String message = "Hello Client"; MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(numMessages); @@ -568,7 +571,7 @@ public void receivedDataForInvalidStreamShouldKillConnection() throws Exception public void invalidInboundHeadersCancelStream() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -593,7 +596,7 @@ public void invalidInboundHeadersCancelStream() throws Exception { public void invalidInboundTrailersPropagateToMetadata() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -613,7 +616,7 @@ public void invalidInboundTrailersPropagateToMetadata() throws Exception { public void readStatus() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); assertContainStream(3); @@ -627,7 +630,7 @@ public void readStatus() throws Exception { public void receiveReset() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); assertContainStream(3); @@ -644,7 +647,7 @@ public void receiveReset() throws Exception { public void receiveResetNoError() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); assertContainStream(3); @@ -665,7 +668,7 @@ public void receiveResetNoError() throws Exception { public void cancelStream() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); getStream(3).cancel(Status.CANCELLED); @@ -680,7 +683,7 @@ public void cancelStream() throws Exception { public void addDefaultUserAgent() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); Header userAgentHeader = new Header(GrpcUtil.USER_AGENT_KEY.name(), @@ -699,7 +702,7 @@ public void addDefaultUserAgent() throws Exception { public void overrideDefaultUserAgent() throws Exception { startTransport(3, null, true, "fakeUserAgent"); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); List
expectedHeaders = Arrays.asList(HTTP_SCHEME_HEADER, METHOD_HEADER, @@ -718,7 +721,7 @@ public void overrideDefaultUserAgent() throws Exception { public void cancelStreamForDeadlineExceeded() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); getStream(3).cancel(Status.DEADLINE_EXCEEDED); @@ -732,7 +735,7 @@ public void writeMessage() throws Exception { initTransport(); final String message = "Hello Server"; MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); InputStream input = new ByteArrayInputStream(message.getBytes(UTF_8)); @@ -773,12 +776,12 @@ public void windowUpdate() throws Exception { initTransport(); MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); - OkHttpClientStream stream1 = + ClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); stream1.request(2); - OkHttpClientStream stream2 = + ClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); stream2.request(2); @@ -843,7 +846,7 @@ public void windowUpdate() throws Exception { public void windowUpdateWithInboundFlowControl() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); int messageLength = INITIAL_WINDOW_SIZE / 2 + 1; @@ -880,7 +883,7 @@ public void windowUpdateWithInboundFlowControl() throws Exception { public void outboundFlowControl() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); @@ -926,7 +929,7 @@ public void outboundFlowControl_smallWindowSize() throws Exception { setInitialWindowSize(initialOutboundWindowSize); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); @@ -969,7 +972,7 @@ public void outboundFlowControl_bigWindowSize() throws Exception { frameHandler().windowUpdate(0, 65535); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); @@ -1005,7 +1008,7 @@ public void outboundFlowControl_bigWindowSize() throws Exception { public void outboundFlowControlWithInitialWindowSizeChange() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); int messageLength = 20; @@ -1051,7 +1054,7 @@ public void outboundFlowControlWithInitialWindowSizeChange() throws Exception { public void outboundFlowControlWithInitialWindowSizeChangeInMiddleOfStream() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); int messageLength = 20; @@ -1086,10 +1089,10 @@ public void stopNormally() throws Exception { initTransport(); MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); - OkHttpClientStream stream1 = + ClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); - OkHttpClientStream stream2 = + ClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); assertEquals(2, activeStreamCount()); @@ -1116,11 +1119,11 @@ public void receiveGoAway() throws Exception { // start 2 streams. MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); - OkHttpClientStream stream1 = + ClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); stream1.request(1); - OkHttpClientStream stream2 = + ClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); stream2.request(1); @@ -1143,7 +1146,7 @@ public void receiveGoAway() throws Exception { // But stream 1 should be able to send. final String sentMessage = "Should I also go away?"; - OkHttpClientStream stream = getStream(3); + ClientStream stream = getStream(3); InputStream input = new ByteArrayInputStream(sentMessage.getBytes(UTF_8)); assertEquals(22, input.available()); stream.writeMessage(input); @@ -1175,7 +1178,7 @@ public void streamIdExhausted() throws Exception { initTransport(startId); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -1211,11 +1214,11 @@ public void pendingStreamSucceed() throws Exception { setMaxConcurrentStreams(1); final MockStreamListener listener1 = new MockStreamListener(); final MockStreamListener listener2 = new MockStreamListener(); - OkHttpClientStream stream1 = + ClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); // The second stream should be pending. - OkHttpClientStream stream2 = + ClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); String sentMessage = "hello"; @@ -1248,7 +1251,7 @@ public void pendingStreamCancelled() throws Exception { initTransport(); setMaxConcurrentStreams(0); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); waitForStreamPending(1); @@ -1267,11 +1270,11 @@ public void pendingStreamFailedByGoAway() throws Exception { setMaxConcurrentStreams(1); final MockStreamListener listener1 = new MockStreamListener(); final MockStreamListener listener2 = new MockStreamListener(); - OkHttpClientStream stream1 = + ClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); // The second stream should be pending. - OkHttpClientStream stream2 = + ClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); @@ -1297,7 +1300,7 @@ public void pendingStreamSucceedAfterShutdown() throws Exception { setMaxConcurrentStreams(0); final MockStreamListener listener = new MockStreamListener(); // The second stream should be pending. - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); waitForStreamPending(1); @@ -1321,15 +1324,15 @@ public void pendingStreamFailedByIdExhausted() throws Exception { final MockStreamListener listener2 = new MockStreamListener(); final MockStreamListener listener3 = new MockStreamListener(); - OkHttpClientStream stream1 = + ClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); // The second and third stream should be pending. - OkHttpClientStream stream2 = + ClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); - OkHttpClientStream stream3 = + ClientStream stream3 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream3.start(listener3); @@ -1353,7 +1356,7 @@ public void pendingStreamFailedByIdExhausted() throws Exception { public void receivingWindowExceeded() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -1405,7 +1408,7 @@ public void duplexStreamingHeadersShouldNotBeFlushed() throws Exception { private void shouldHeadersBeFlushed(boolean shouldBeFlushed) throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); verify(frameWriter, timeout(TIME_OUT_MS)).synStream( @@ -1422,7 +1425,7 @@ private void shouldHeadersBeFlushed(boolean shouldBeFlushed) throws Exception { public void receiveDataWithoutHeader() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -1445,7 +1448,7 @@ public void receiveDataWithoutHeader() throws Exception { public void receiveDataWithoutHeaderAndTrailer() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -1469,7 +1472,7 @@ public void receiveDataWithoutHeaderAndTrailer() throws Exception { public void receiveLongEnoughDataWithoutHeaderAndTrailer() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -1491,7 +1494,7 @@ public void receiveLongEnoughDataWithoutHeaderAndTrailer() throws Exception { public void receiveDataForUnknownStreamUpdateConnectionWindow() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.cancel(Status.CANCELLED); @@ -1520,7 +1523,7 @@ public void receiveDataForUnknownStreamUpdateConnectionWindow() throws Exception public void receiveWindowUpdateForUnknownStream() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.cancel(Status.CANCELLED); @@ -1540,7 +1543,7 @@ public void receiveWindowUpdateForUnknownStream() throws Exception { public void shouldBeInitiallyReady() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); assertTrue(stream.isReady()); @@ -1558,7 +1561,7 @@ public void notifyOnReady() throws Exception { AbstractStream.TransportState.DEFAULT_ONREADY_THRESHOLD - HEADER_LENGTH - 1; setInitialWindowSize(0); MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); assertTrue(stream.isReady()); @@ -1726,7 +1729,8 @@ public void invalidAuthorityPropagates() { "userAgent", EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable, channelCredentials); + tooManyPingsRunnable, + null); String host = clientTransport.getOverridenHost(); int port = clientTransport.getOverridenPort(); @@ -1744,7 +1748,8 @@ public void unreachableServer() throws Exception { "userAgent", EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable, channelCredentials); + tooManyPingsRunnable, + null); ManagedClientTransport.Listener listener = mock(ManagedClientTransport.Listener.class); clientTransport.start(listener); @@ -1774,7 +1779,8 @@ public void customSocketFactory() throws Exception { "userAgent", EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable, channelCredentials); + tooManyPingsRunnable, + null); ManagedClientTransport.Listener listener = mock(ManagedClientTransport.Listener.class); clientTransport.start(listener); @@ -1799,7 +1805,8 @@ public void proxy_200() throws Exception { .setTargetAddress(targetAddress) .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) .build(), - tooManyPingsRunnable, channelCredentials); + tooManyPingsRunnable, + null); clientTransport.start(transportListener); Socket sock = serverSocket.accept(); @@ -1848,7 +1855,8 @@ public void proxy_500() throws Exception { .setTargetAddress(targetAddress) .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) .build(), - tooManyPingsRunnable, channelCredentials); + tooManyPingsRunnable, + null); clientTransport.start(transportListener); Socket sock = serverSocket.accept(); @@ -1896,7 +1904,8 @@ public void proxy_immediateServerClose() throws Exception { .setTargetAddress(targetAddress) .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) .build(), - tooManyPingsRunnable, channelCredentials); + tooManyPingsRunnable, + null); clientTransport.start(transportListener); Socket sock = serverSocket.accept(); @@ -1927,7 +1936,8 @@ public void proxy_serverHangs() throws Exception { .setTargetAddress(targetAddress) .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) .build(), - tooManyPingsRunnable, channelCredentials); + tooManyPingsRunnable, + null); clientTransport.proxySocketTimeout = 10; clientTransport.start(transportListener); @@ -1994,13 +2004,13 @@ public void goAway_streamListenerRpcProgress() throws Exception { MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); MockStreamListener listener3 = new MockStreamListener(); - OkHttpClientStream stream1 = + ClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); - OkHttpClientStream stream2 = + ClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); - OkHttpClientStream stream3 = + ClientStream stream3 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream3.start(listener3); waitForStreamPending(1); @@ -2034,13 +2044,13 @@ public void reset_streamListenerRpcProgress() throws Exception { MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); MockStreamListener listener3 = new MockStreamListener(); - OkHttpClientStream stream1 = + ClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); - OkHttpClientStream stream2 = + ClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); - OkHttpClientStream stream3 = + ClientStream stream3 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream3.start(listener3); @@ -2076,13 +2086,13 @@ public void shutdownNow_streamListenerRpcProgress() throws Exception { MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); MockStreamListener listener3 = new MockStreamListener(); - OkHttpClientStream stream1 = + ClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); - OkHttpClientStream stream2 = + ClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); - OkHttpClientStream stream3 = + ClientStream stream3 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream3.start(listener3); waitForStreamPending(1); @@ -2108,10 +2118,12 @@ public void finishedStreamRemovedFromInUseState() throws Exception { setMaxConcurrentStreams(1); final MockStreamListener listener = new MockStreamListener(); OkHttpClientStream stream = - clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); + (OkHttpClientStream) clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, + tracers); stream.start(listener); OkHttpClientStream pendingStream = - clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); + (OkHttpClientStream) clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, + tracers); pendingStream.start(listener); waitForStreamPending(1); clientTransport.finishStream(stream.transportState().id(), Status.OK, PROCESSED, @@ -2151,7 +2163,7 @@ private void waitForStreamPending(int expected) throws Exception { private void assertNewStreamFail() throws Exception { MockStreamListener listener = new MockStreamListener(); - OkHttpClientStream stream = + ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); listener.waitUntilStreamClosed(); From 1e072d4b859b46607bd93516257813aff4cbad93 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Mon, 9 Dec 2024 12:18:05 +0000 Subject: [PATCH 19/70] In-progress review comments. --- .../io/grpc/internal/CertificateUtils.java | 52 +++++++++++++++++++ examples/example-tls/build.gradle | 5 +- .../io/grpc/netty/NettyClientTransport.java | 39 ++++++-------- .../io/grpc/netty/ProtocolNegotiator.java | 13 +++++ .../io/grpc/netty/ProtocolNegotiators.java | 33 +++++++++++- .../java/io/grpc/util/CertificateUtils.java | 34 ------------ 6 files changed, 114 insertions(+), 62 deletions(-) create mode 100644 core/src/main/java/io/grpc/internal/CertificateUtils.java diff --git a/core/src/main/java/io/grpc/internal/CertificateUtils.java b/core/src/main/java/io/grpc/internal/CertificateUtils.java new file mode 100644 index 00000000000..97875ab8de8 --- /dev/null +++ b/core/src/main/java/io/grpc/internal/CertificateUtils.java @@ -0,0 +1,52 @@ +package io.grpc.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; +import javax.security.auth.x500.X500Principal; + +public class CertificateUtils { + /** + * Creates a X509ExtendedTrustManager using the provided CA certs if applicable for the + * certificate type. + */ + public static Optional getX509ExtendedTrustManager(InputStream rootCerts) + throws GeneralSecurityException { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + try { + ks.load(null, null); + } catch (IOException ex) { + // Shouldn't really happen, as we're not loading any data. + throw new GeneralSecurityException(ex); + } + X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); + for (X509Certificate cert : certs) { + X500Principal principal = cert.getSubjectX500Principal(); + ks.setCertificateEntry(principal.getName("RFC2253"), cert); + } + + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(ks); + return Arrays.stream(trustManagerFactory.getTrustManagers()) + .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + } + + private static X509Certificate[] getX509Certificates(InputStream inputStream) + throws CertificateException { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + Collection certs = factory.generateCertificates(inputStream); + return certs.toArray(new X509Certificate[0]); + } +} diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 9248749ea5e..c070cd3436e 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -30,7 +30,6 @@ def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" - implementation "io.grpc:grpc-api:${grpcVersion}" compileOnly "org.apache.tomcat:annotations-api:6.0.53" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" } @@ -75,8 +74,6 @@ application { applicationDistribution.into('bin') { from(helloWorldTlsServer) from(helloWorldTlsClient) - filePermissions { - unix(0755) - } + fileMode = 0755 } } diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index 6415d701cf2..e802141151b 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -203,32 +203,25 @@ public ClientStream newStream( if (channel == null) { return new FailingClientStream(statusExplainingWhyTheChannelIsNull, tracers); } - if (negotiator instanceof ClientTlsProtocolNegotiator && callOptions.getAuthority() != null) { - ClientTlsProtocolNegotiator clientTlsProtocolNegotiator = - (ClientTlsProtocolNegotiator) negotiator; - if (!clientTlsProtocolNegotiator.canVerifyAuthorityOverride()) { - return new FailingClientStream(Status.INTERNAL.withDescription( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), - tracers); - } - boolean peerVerified; - if (authoritiesAllowedForPeer.containsKey(callOptions.getAuthority())) { - peerVerified = authoritiesAllowedForPeer.get(callOptions.getAuthority()); - } else { - try { - clientTlsProtocolNegotiator.verifyAuthorityAllowedForPeerCert(callOptions.getAuthority()); - peerVerified = true; - } catch (SSLPeerUnverifiedException | CertificateException e) { - peerVerified = false; - logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.", - callOptions.getAuthority()); - } - authoritiesAllowedForPeer.put(callOptions.getAuthority(), peerVerified); - } - if (!peerVerified) { + try { + if (callOptions.getAuthority() != null && !negotiator.mayBeVerifyAuthority( + callOptions.getAuthority())) { + logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.", + callOptions.getAuthority()); return new FailingClientStream(Status.INTERNAL.withDescription( "Peer hostname verification failed for authority"), tracers); } + } catch (UnsupportedOperationException ex) { + logger.log(Level.FINE, "Can't allow authority override in rpc when X509ExtendedTrustManager is not available.", + callOptions.getAuthority()); + return new FailingClientStream(Status.INTERNAL.withDescription( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), + tracers); + } catch (SSLPeerUnverifiedException | CertificateException ex) { + logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.", + callOptions.getAuthority()); + return new FailingClientStream(Status.INTERNAL.withDescription( + "Peer hostname verification failed for authority"), tracers); } StatsTraceContext statsTraceCtx = StatsTraceContext.newClientContext(tracers, getAttributes(), headers); diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java index 8a2c6f104b2..4054e482c92 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java @@ -19,7 +19,9 @@ import io.grpc.internal.ObjectPool; import io.netty.channel.ChannelHandler; import io.netty.util.AsciiString; +import java.security.cert.CertificateException; import java.util.concurrent.Executor; +import javax.net.ssl.SSLPeerUnverifiedException; /** * An class that provides a Netty handler to control protocol negotiation. @@ -63,4 +65,15 @@ interface ServerFactory { */ ProtocolNegotiator newNegotiator(ObjectPool offloadExecutorPool); } + + /** + * Verify the authority against peer if applicable depending on the transport credential type. + * @throws UnsupportedOperationException if the verification should happen but the required + * type of TrustManager could not be found. + * @throws SSLPeerUnverifiedException if peer verification failed + * @throws CertificateException if certificates have a problem + */ + default boolean mayBeVerifyAuthority(String authority) throws SSLPeerUnverifiedException, CertificateException { + return true; + } } diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 37170dfcce5..595e594de94 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import static io.grpc.util.CertificateUtils.getX509ExtendedTrustManager; +import static io.grpc.internal.CertificateUtils.getX509ExtendedTrustManager; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; @@ -42,6 +42,7 @@ import io.grpc.Status; import io.grpc.TlsChannelCredentials; import io.grpc.TlsServerCredentials; +import io.grpc.internal.FailingClientStream; import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; @@ -85,6 +86,7 @@ import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; @@ -640,6 +642,35 @@ public void setSslEngine(SSLEngine sslEngine) { boolean hasX509ExtendedTrustManager() { return x509ExtendedTrustManager != null; } + + public boolean mayBeVerifyAuthority(@Nonnull String authority) { + ClientTlsProtocolNegotiator clientTlsProtocolNegotiator = + (ClientTlsProtocolNegotiator) negotiator; + if (!clientTlsProtocolNegotiator.canVerifyAuthorityOverride()) { + return new FailingClientStream(Status.INTERNAL.withDescription( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), + tracers); + } + boolean peerVerified; + if (authoritiesAllowedForPeer.containsKey(callOptions.getAuthority())) { + peerVerified = authoritiesAllowedForPeer.get(callOptions.getAuthority()); + } else { + try { + clientTlsProtocolNegotiator.verifyAuthorityAllowedForPeerCert( + callOptions.getAuthority()); + peerVerified = true; + } catch (SSLPeerUnverifiedException | CertificateException e) { + peerVerified = false; + logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.", + callOptions.getAuthority()); + } + authoritiesAllowedForPeer.put(callOptions.getAuthority(), peerVerified); + } + if (!peerVerified) { + return new FailingClientStream(Status.INTERNAL.withDescription( + "Peer hostname verification failed for authority"), tracers); + } + } } static final class ClientTlsHandler extends ProtocolNegotiationHandler { diff --git a/util/src/main/java/io/grpc/util/CertificateUtils.java b/util/src/main/java/io/grpc/util/CertificateUtils.java index 400c6a54a5a..e7082f177ab 100644 --- a/util/src/main/java/io/grpc/util/CertificateUtils.java +++ b/util/src/main/java/io/grpc/util/CertificateUtils.java @@ -23,9 +23,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; -import java.security.GeneralSecurityException; import java.security.KeyFactory; -import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; @@ -34,13 +32,7 @@ import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Arrays; import java.util.Collection; -import java.util.Optional; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedTrustManager; -import javax.security.auth.x500.X500Principal; /** * Contains certificate/key PEM file utility method(s). @@ -99,31 +91,5 @@ public static PrivateKey getPrivateKey(InputStream inputStream) } } } - - /** - * Creates a X509ExtendedTrustManager using the provided CA certs if applicable for the - * certificate type. - */ - public static Optional getX509ExtendedTrustManager(InputStream rootCerts) - throws GeneralSecurityException { - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - try { - ks.load(null, null); - } catch (IOException ex) { - // Shouldn't really happen, as we're not loading any data. - throw new GeneralSecurityException(ex); - } - X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); - for (X509Certificate cert : certs) { - X500Principal principal = cert.getSubjectX500Principal(); - ks.setCertificateEntry(principal.getName("RFC2253"), cert); - } - - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(ks); - return Arrays.stream(trustManagerFactory.getTrustManagers()) - .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); - } } From 32104cebf34707c25a5095371a076574aafb988d Mon Sep 17 00:00:00 2001 From: Kannan J Date: Mon, 9 Dec 2024 18:41:08 +0000 Subject: [PATCH 20/70] Address review comments. --- .../io/grpc/internal/CertificateUtils.java | 19 +++++ .../io/grpc/netty/NettyClientTransport.java | 19 +++-- .../io/grpc/netty/ProtocolNegotiator.java | 5 +- .../io/grpc/netty/ProtocolNegotiators.java | 69 +++++++++---------- .../grpc/netty/NettyClientTransportTest.java | 4 +- .../grpc/netty/ProtocolNegotiatorsTest.java | 43 ++++++++++++ 6 files changed, 109 insertions(+), 50 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/CertificateUtils.java b/core/src/main/java/io/grpc/internal/CertificateUtils.java index 97875ab8de8..1d9d9427f05 100644 --- a/core/src/main/java/io/grpc/internal/CertificateUtils.java +++ b/core/src/main/java/io/grpc/internal/CertificateUtils.java @@ -1,3 +1,19 @@ +/* + * Copyright 2024 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.grpc.internal; import java.io.IOException; @@ -16,6 +32,9 @@ import javax.net.ssl.X509ExtendedTrustManager; import javax.security.auth.x500.X500Principal; +/** + * Contains certificate/key PEM file utility method(s) for internal usage. + */ public class CertificateUtils { /** * Creates a X509ExtendedTrustManager using the provided CA certs if applicable for the diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index e802141151b..a4849ae3696 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -45,7 +45,6 @@ import io.grpc.internal.StatsTraceContext; import io.grpc.internal.TransportTracer; import io.grpc.netty.NettyChannelBuilder.LocalSocketPicker; -import io.grpc.netty.ProtocolNegotiators.ClientTlsProtocolNegotiator; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFactory; @@ -63,7 +62,6 @@ import java.nio.channels.ClosedChannelException; import java.security.cert.CertificateException; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -112,8 +110,6 @@ class NettyClientTransport implements ConnectionClientTransport { private final ChannelLogger channelLogger; private final boolean useGetForSafeMethods; private final Ticker ticker; - private final ConcurrentHashMap authoritiesAllowedForPeer = - new ConcurrentHashMap<>(); NettyClientTransport( SocketAddress address, @@ -206,22 +202,23 @@ public ClientStream newStream( try { if (callOptions.getAuthority() != null && !negotiator.mayBeVerifyAuthority( callOptions.getAuthority())) { - logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.", + String errMsg = String.format("Peer hostname verification failed for authority '%s'.", callOptions.getAuthority()); - return new FailingClientStream(Status.INTERNAL.withDescription( - "Peer hostname verification failed for authority"), tracers); + logger.log(Level.FINE, errMsg); + return new FailingClientStream(Status.INTERNAL.withDescription(errMsg), tracers); } } catch (UnsupportedOperationException ex) { - logger.log(Level.FINE, "Can't allow authority override in rpc when X509ExtendedTrustManager is not available.", + logger.log(Level.FINE, + "Can't allow authority override in rpc when X509ExtendedTrustManager is not available.", callOptions.getAuthority()); return new FailingClientStream(Status.INTERNAL.withDescription( "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), tracers); } catch (SSLPeerUnverifiedException | CertificateException ex) { - logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.", + String errMsg = String.format("Peer hostname verification failed for authority '%s'.", callOptions.getAuthority()); - return new FailingClientStream(Status.INTERNAL.withDescription( - "Peer hostname verification failed for authority"), tracers); + logger.log(Level.FINE, errMsg, ex); + return new FailingClientStream(Status.INTERNAL.withDescription(errMsg), tracers); } StatsTraceContext statsTraceCtx = StatsTraceContext.newClientContext(tracers, getAttributes(), headers); diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java index 4054e482c92..221c5b63f18 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java @@ -69,11 +69,12 @@ interface ServerFactory { /** * Verify the authority against peer if applicable depending on the transport credential type. * @throws UnsupportedOperationException if the verification should happen but the required - * type of TrustManager could not be found. + * type of TrustManager could not be found. * @throws SSLPeerUnverifiedException if peer verification failed * @throws CertificateException if certificates have a problem */ - default boolean mayBeVerifyAuthority(String authority) throws SSLPeerUnverifiedException, CertificateException { + default boolean mayBeVerifyAuthority(String authority) + throws SSLPeerUnverifiedException, CertificateException { return true; } } diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 595e594de94..28b44bf90e4 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -42,7 +42,6 @@ import io.grpc.Status; import io.grpc.TlsChannelCredentials; import io.grpc.TlsServerCredentials; -import io.grpc.internal.FailingClientStream; import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; @@ -81,6 +80,7 @@ import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.EnumSet; +import java.util.LinkedHashMap; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; @@ -574,6 +574,8 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { + private static final int MAX_AUTHORITIES_CACHE_SIZE = 100; + private final LinkedHashMap authoritiesAllowedForPeer = new LinkedHashMap<>(); private SSLEngine sslEngine; public ClientTlsProtocolNegotiator(SslContext sslContext, @@ -615,13 +617,43 @@ public void close() { } } + @Override + synchronized public boolean mayBeVerifyAuthority(@Nonnull String authority) { + if (!canVerifyAuthorityOverride()) { + throw new UnsupportedOperationException( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not" + + "available."); + } + boolean peerVerified; + if (authoritiesAllowedForPeer.containsKey(authority)) { + peerVerified = authoritiesAllowedForPeer.get(authority); + } else { + try { + verifyAuthorityAllowedForPeerCert(authority); + peerVerified = true; + } catch (SSLPeerUnverifiedException | CertificateException e) { + peerVerified = false; + } + authoritiesAllowedForPeer.put(authority, peerVerified); + if (authoritiesAllowedForPeer.size() > MAX_AUTHORITIES_CACHE_SIZE) { + authoritiesAllowedForPeer.remove( + authoritiesAllowedForPeer.entrySet().iterator().next().getKey()); + } + } + return peerVerified; + } + + public void setSslEngine(SSLEngine sslEngine) { + this.sslEngine = sslEngine; + } + boolean canVerifyAuthorityOverride() { // sslEngine won't be set when creating ClientTlsHandlder from InternalProtocolNegotiators // for example. return sslEngine != null && x509ExtendedTrustManager != null; } - public void verifyAuthorityAllowedForPeerCert(String authority) + private void verifyAuthorityAllowedForPeerCert(String authority) throws SSLPeerUnverifiedException, CertificateException { SSLEngine sslEngineWrapper = new SslEngineWrapper(sslEngine, authority); // The typecasting of Certificate to X509Certificate should work because this method will only @@ -634,43 +666,10 @@ public void verifyAuthorityAllowedForPeerCert(String authority) x509ExtendedTrustManager.checkServerTrusted(x509PeerCertificates, "RSA", sslEngineWrapper); } - public void setSslEngine(SSLEngine sslEngine) { - this.sslEngine = sslEngine; - } - @VisibleForTesting boolean hasX509ExtendedTrustManager() { return x509ExtendedTrustManager != null; } - - public boolean mayBeVerifyAuthority(@Nonnull String authority) { - ClientTlsProtocolNegotiator clientTlsProtocolNegotiator = - (ClientTlsProtocolNegotiator) negotiator; - if (!clientTlsProtocolNegotiator.canVerifyAuthorityOverride()) { - return new FailingClientStream(Status.INTERNAL.withDescription( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), - tracers); - } - boolean peerVerified; - if (authoritiesAllowedForPeer.containsKey(callOptions.getAuthority())) { - peerVerified = authoritiesAllowedForPeer.get(callOptions.getAuthority()); - } else { - try { - clientTlsProtocolNegotiator.verifyAuthorityAllowedForPeerCert( - callOptions.getAuthority()); - peerVerified = true; - } catch (SSLPeerUnverifiedException | CertificateException e) { - peerVerified = false; - logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.", - callOptions.getAuthority()); - } - authoritiesAllowedForPeer.put(callOptions.getAuthority(), peerVerified); - } - if (!peerVerified) { - return new FailingClientStream(Status.INTERNAL.withDescription( - "Peer hostname verification failed for authority"), tracers); - } - } } static final class ClientTlsHandler extends ProtocolNegotiationHandler { diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 60e95466a5b..41722609614 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -891,8 +891,8 @@ Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.i InsightBuilder insightBuilder = new InsightBuilder(); stream.appendTimeoutInsight(insightBuilder); assertThat(insightBuilder.toString()).contains( - "Status{code=INTERNAL, description=Peer hostname verification failed for authority, " - + "cause=null}"); + "Status{code=INTERNAL, description=Peer hostname verification failed for authority" + + " 'foo.test.google.in'., cause=null}"); } @Test diff --git a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java index cba3757e741..578e47df7a9 100644 --- a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java +++ b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java @@ -26,10 +26,12 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import io.grpc.Attributes; import io.grpc.CallCredentials; @@ -66,6 +68,7 @@ import io.grpc.netty.ProtocolNegotiators.ClientTlsProtocolNegotiator; import io.grpc.netty.ProtocolNegotiators.HostPort; import io.grpc.netty.ProtocolNegotiators.ServerTlsHandler; +import io.grpc.netty.ProtocolNegotiators.SslEngineWrapper; import io.grpc.netty.ProtocolNegotiators.WaitUntilActiveHandler; import io.grpc.testing.TlsTesting; import io.grpc.util.CertificateUtils; @@ -118,6 +121,7 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayDeque; @@ -140,6 +144,7 @@ import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; @@ -1032,6 +1037,44 @@ private ClientTlsProtocolNegotiator getClientTlsProtocolNegotiator() throws SSLE null, Optional.empty(), null); } + @Test + public void allowedAuthoritiesForTransport_LruCache() throws SSLException, CertificateException { + X509ExtendedTrustManager mockX509ExtendedTrustManager = mock(X509ExtendedTrustManager.class); + ClientTlsProtocolNegotiator negotiator = new ClientTlsProtocolNegotiator( + GrpcSslContexts.forClient().trustManager( + TlsTesting.loadCert("ca.pem")).build(), + null, Optional.empty(), mockX509ExtendedTrustManager); + SSLEngine mockSslEngine = mock(SSLEngine.class); + negotiator.setSslEngine(mockSslEngine); + SSLSession mockSslSession = mock(SSLSession.class); + when(mockSslEngine.getSession()).thenReturn(mockSslSession); + when(mockSslSession.getPeerCertificates()).thenReturn(new Certificate[0]); + + // Fill the cache + for (int i = 0; i < 100; i++) { + boolean unused = negotiator.mayBeVerifyAuthority("authority" + i); + } + // Should use value from the cache. + boolean unused = negotiator.mayBeVerifyAuthority("authority0"); + // Should evict authority0. + unused = negotiator.mayBeVerifyAuthority("authority100"); + // Should call TrustManager as the cached value has been evicted for this authority value. + unused = negotiator.mayBeVerifyAuthority("authority0"); + + ArgumentCaptor sslEngineWrapperArgumentCaptor = + ArgumentCaptor.forClass(SslEngineWrapper.class); + verify(mockX509ExtendedTrustManager, times(102)).checkServerTrusted(any(), eq("RSA"), + sslEngineWrapperArgumentCaptor.capture()); + List sslEngineWrappersCaptured = + sslEngineWrapperArgumentCaptor.getAllValues(); + assertThat(sslEngineWrappersCaptured).hasSize(102); + for (int i = 0; i < 100; i++) { + assertThat(sslEngineWrappersCaptured.get(i).getPeerHost()).isEqualTo("authority" + i); + } + assertThat(sslEngineWrappersCaptured.get(100).getPeerHost()).isEqualTo("authority100"); + assertThat(sslEngineWrappersCaptured.get(101).getPeerHost()).isEqualTo("authority0"); + } + @Test public void engineLog() { ChannelHandler handler = new ServerTlsHandler(grpcHandler, sslContext, null); From 95aae4a82a7c39dc69fc54d82bc7a3d1b0f70a8d Mon Sep 17 00:00:00 2001 From: Kannan J Date: Mon, 9 Dec 2024 18:42:45 +0000 Subject: [PATCH 21/70] revert unintended --- examples/example-tls/build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index c070cd3436e..1eb51182309 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -74,6 +74,8 @@ application { applicationDistribution.into('bin') { from(helloWorldTlsServer) from(helloWorldTlsClient) - fileMode = 0755 + filePermissions { + unix(0755) + } } } From c0327b5380b90b2274fdab78cb0ee23fba48dc3b Mon Sep 17 00:00:00 2001 From: Kannan J Date: Mon, 9 Dec 2024 18:44:56 +0000 Subject: [PATCH 22/70] revert unintended --- netty/BUILD.bazel | 1 - netty/build.gradle | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/netty/BUILD.bazel b/netty/BUILD.bazel index 9521c60d01d..9fe52ea5868 100644 --- a/netty/BUILD.bazel +++ b/netty/BUILD.bazel @@ -12,7 +12,6 @@ java_library( deps = [ "//api", "//core:internal", - "//util", artifact("com.google.code.findbugs:jsr305"), artifact("com.google.errorprone:error_prone_annotations"), artifact("com.google.guava:guava"), diff --git a/netty/build.gradle b/netty/build.gradle index 1a859a03bd6..38696fa740d 100644 --- a/netty/build.gradle +++ b/netty/build.gradle @@ -18,8 +18,7 @@ tasks.named("jar").configure { dependencies { api project(':grpc-api'), libraries.netty.codec.http2 - implementation project(':grpc-util'), - project(':grpc-core'), + implementation project(':grpc-core'), libs.netty.handler.proxy, libraries.guava, libraries.errorprone.annotations, From b24a6055a2c7fd51c3942c04065881554c0d8dc1 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Fri, 13 Dec 2024 20:44:57 +0530 Subject: [PATCH 23/70] Address review comments. --- netty/build.gradle | 2 +- .../io/grpc/netty/NettyClientTransport.java | 28 +- .../java/io/grpc/netty/NoopSslEngine.java | 151 +++++++++++ .../java/io/grpc/netty/NoopSslSession.java | 132 +++++++++ .../io/grpc/netty/ProtocolNegotiator.java | 12 +- .../io/grpc/netty/ProtocolNegotiators.java | 251 ++---------------- .../grpc/netty/NettyClientTransportTest.java | 6 +- .../grpc/netty/ProtocolNegotiatorsTest.java | 8 +- 8 files changed, 321 insertions(+), 269 deletions(-) create mode 100644 netty/src/main/java/io/grpc/netty/NoopSslEngine.java create mode 100644 netty/src/main/java/io/grpc/netty/NoopSslSession.java diff --git a/netty/build.gradle b/netty/build.gradle index 38696fa740d..5533038c85b 100644 --- a/netty/build.gradle +++ b/netty/build.gradle @@ -18,7 +18,7 @@ tasks.named("jar").configure { dependencies { api project(':grpc-api'), libraries.netty.codec.http2 - implementation project(':grpc-core'), + implementation project(':grpc-core'), libs.netty.handler.proxy, libraries.guava, libraries.errorprone.annotations, diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index a4849ae3696..f1befbd24e6 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -60,20 +60,15 @@ import io.netty.util.concurrent.GenericFutureListener; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; -import java.security.cert.CertificateException; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.annotation.Nullable; -import javax.net.ssl.SSLPeerUnverifiedException; /** * A Netty-based {@link ConnectionClientTransport} implementation. */ class NettyClientTransport implements ConnectionClientTransport { - private static final Logger logger = Logger.getLogger(NettyClientTransport.class.getName()); private final InternalLogId logId; private final Map, ?> channelOptions; @@ -199,26 +194,11 @@ public ClientStream newStream( if (channel == null) { return new FailingClientStream(statusExplainingWhyTheChannelIsNull, tracers); } - try { - if (callOptions.getAuthority() != null && !negotiator.mayBeVerifyAuthority( - callOptions.getAuthority())) { - String errMsg = String.format("Peer hostname verification failed for authority '%s'.", - callOptions.getAuthority()); - logger.log(Level.FINE, errMsg); - return new FailingClientStream(Status.INTERNAL.withDescription(errMsg), tracers); + if (callOptions.getAuthority() != null) { + Status verificationStatus = negotiator.verifyAuthority(callOptions.getAuthority()); + if (verificationStatus != Status.OK) { + return new FailingClientStream(verificationStatus, tracers); } - } catch (UnsupportedOperationException ex) { - logger.log(Level.FINE, - "Can't allow authority override in rpc when X509ExtendedTrustManager is not available.", - callOptions.getAuthority()); - return new FailingClientStream(Status.INTERNAL.withDescription( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not available"), - tracers); - } catch (SSLPeerUnverifiedException | CertificateException ex) { - String errMsg = String.format("Peer hostname verification failed for authority '%s'.", - callOptions.getAuthority()); - logger.log(Level.FINE, errMsg, ex); - return new FailingClientStream(Status.INTERNAL.withDescription(errMsg), tracers); } StatsTraceContext statsTraceCtx = StatsTraceContext.newClientContext(tracers, getAttributes(), headers); diff --git a/netty/src/main/java/io/grpc/netty/NoopSslEngine.java b/netty/src/main/java/io/grpc/netty/NoopSslEngine.java new file mode 100644 index 00000000000..b0a5f6936e3 --- /dev/null +++ b/netty/src/main/java/io/grpc/netty/NoopSslEngine.java @@ -0,0 +1,151 @@ +/* + * Copyright 2024 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.netty; + +import java.nio.ByteBuffer; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; + +/** + * A no-op implementation of SslEngine, to facilitate overriding only the required methods in + * specific implementations. + */ +public class NoopSslEngine extends SSLEngine { + @Override + public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int length, ByteBuffer dst) + throws SSLException { + return null; + } + + @Override + public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts, int offset, int length) + throws SSLException { + return null; + } + + @Override + public Runnable getDelegatedTask() { + return null; + } + + @Override + public void closeInbound() throws SSLException { + + } + + @Override + public boolean isInboundDone() { + return false; + } + + @Override + public void closeOutbound() { + + } + + @Override + public boolean isOutboundDone() { + return false; + } + + @Override + public String[] getSupportedCipherSuites() { + return new String[0]; + } + + @Override + public String[] getEnabledCipherSuites() { + return new String[0]; + } + + @Override + public void setEnabledCipherSuites(String[] suites) { + + } + + @Override + public String[] getSupportedProtocols() { + return new String[0]; + } + + @Override + public String[] getEnabledProtocols() { + return new String[0]; + } + + @Override + public void setEnabledProtocols(String[] protocols) { + + } + + @Override + public SSLSession getSession() { + return null; + } + + @Override + public void beginHandshake() throws SSLException { + + } + + @Override + public SSLEngineResult.HandshakeStatus getHandshakeStatus() { + return null; + } + + @Override + public void setUseClientMode(boolean mode) { + + } + + @Override + public boolean getUseClientMode() { + return false; + } + + @Override + public void setNeedClientAuth(boolean need) { + + } + + @Override + public boolean getNeedClientAuth() { + return false; + } + + @Override + public void setWantClientAuth(boolean want) { + + } + + @Override + public boolean getWantClientAuth() { + return false; + } + + @Override + public void setEnableSessionCreation(boolean flag) { + + } + + @Override + public boolean getEnableSessionCreation() { + return false; + } +} diff --git a/netty/src/main/java/io/grpc/netty/NoopSslSession.java b/netty/src/main/java/io/grpc/netty/NoopSslSession.java new file mode 100644 index 00000000000..647a7deb7ab --- /dev/null +++ b/netty/src/main/java/io/grpc/netty/NoopSslSession.java @@ -0,0 +1,132 @@ +/* + * Copyright 2024 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.netty; + +import java.security.Principal; +import java.security.cert.Certificate; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; + +/** A no-op ssl session, to facilitate overriding only the required methods in specific + * implementations. + */ +class NoopSslSession implements SSLSession { + @Override + public byte[] getId() { + return new byte[0]; + } + + @Override + public SSLSessionContext getSessionContext() { + return null; + } + + @Override + @SuppressWarnings("deprecation") + public javax.security.cert.X509Certificate[] getPeerCertificateChain() { + throw new UnsupportedOperationException("This method is deprecated and marked for removal. " + + "Use the getPeerCertificates() method instead."); + } + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public long getLastAccessedTime() { + return 0; + } + + @Override + public void invalidate() { + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public void putValue(String s, Object o) { + } + + @Override + public Object getValue(String s) { + return null; + } + + @Override + public void removeValue(String s) { + } + + @Override + public String[] getValueNames() { + return new String[0]; + } + + @Override + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { + return new Certificate[0]; + } + + @Override + public Certificate[] getLocalCertificates() { + return new Certificate[0]; + } + + @Override + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + return null; + } + + @Override + public Principal getLocalPrincipal() { + return null; + } + + @Override + public String getCipherSuite() { + return null; + } + + @Override + public String getProtocol() { + return null; + } + + @Override + public String getPeerHost() { + return null; + } + + @Override + public int getPeerPort() { + return 0; + } + + @Override + public int getPacketBufferSize() { + return 0; + } + + @Override + public int getApplicationBufferSize() { + return 0; + } +} diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java index 221c5b63f18..4c7aa653c3c 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java @@ -16,12 +16,11 @@ package io.grpc.netty; +import io.grpc.Status; import io.grpc.internal.ObjectPool; import io.netty.channel.ChannelHandler; import io.netty.util.AsciiString; -import java.security.cert.CertificateException; import java.util.concurrent.Executor; -import javax.net.ssl.SSLPeerUnverifiedException; /** * An class that provides a Netty handler to control protocol negotiation. @@ -68,13 +67,8 @@ interface ServerFactory { /** * Verify the authority against peer if applicable depending on the transport credential type. - * @throws UnsupportedOperationException if the verification should happen but the required - * type of TrustManager could not be found. - * @throws SSLPeerUnverifiedException if peer verification failed - * @throws CertificateException if certificates have a problem */ - default boolean mayBeVerifyAuthority(String authority) - throws SSLPeerUnverifiedException, CertificateException { - return true; + default Status verifyAuthority(String authority) { + return Status.UNAVAILABLE; } } diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 28b44bf90e4..995e82a5a6b 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -70,17 +70,16 @@ import java.io.ByteArrayInputStream; import java.net.SocketAddress; import java.net.URI; -import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.security.GeneralSecurityException; import java.security.KeyStore; -import java.security.Principal; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.EnumSet; import java.util.LinkedHashMap; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; @@ -89,13 +88,10 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngineResult; -import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSessionContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; @@ -573,9 +569,13 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws } static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { - - private static final int MAX_AUTHORITIES_CACHE_SIZE = 100; - private final LinkedHashMap authoritiesAllowedForPeer = new LinkedHashMap<>(); + private final LinkedHashMap peerVerificationResults = + new LinkedHashMap() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > 100; + } + }; private SSLEngine sslEngine; public ClientTlsProtocolNegotiator(SslContext sslContext, @@ -618,29 +618,27 @@ public void close() { } @Override - synchronized public boolean mayBeVerifyAuthority(@Nonnull String authority) { + public synchronized Status verifyAuthority(@Nonnull String authority) { if (!canVerifyAuthorityOverride()) { - throw new UnsupportedOperationException( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not" - + "available."); + return Status.FAILED_PRECONDITION.withDescription( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not " + + "available"); } - boolean peerVerified; - if (authoritiesAllowedForPeer.containsKey(authority)) { - peerVerified = authoritiesAllowedForPeer.get(authority); + if (peerVerificationResults.containsKey(authority)) { + return peerVerificationResults.get(authority); } else { + Status peerVerificationStatus; try { verifyAuthorityAllowedForPeerCert(authority); - peerVerified = true; + peerVerificationStatus = Status.OK; } catch (SSLPeerUnverifiedException | CertificateException e) { - peerVerified = false; - } - authoritiesAllowedForPeer.put(authority, peerVerified); - if (authoritiesAllowedForPeer.size() > MAX_AUTHORITIES_CACHE_SIZE) { - authoritiesAllowedForPeer.remove( - authoritiesAllowedForPeer.entrySet().iterator().next().getKey()); + peerVerificationStatus = Status.UNAVAILABLE.withDescription( + String.format("Peer hostname verification failed for authority '%s'", + authority)); } + peerVerificationResults.put(authority, peerVerificationStatus); + return peerVerificationStatus; } - return peerVerified; } public void setSslEngine(SSLEngine sslEngine) { @@ -1210,8 +1208,7 @@ protected final void fireProtocolNegotiationEvent(ChannelHandlerContext ctx) { } } - static final class SslEngineWrapper extends SSLEngine { - + static final class SslEngineWrapper extends NoopSslEngine { private final SSLEngine sslEngine; private final String peerHost; @@ -1234,220 +1231,18 @@ public SSLSession getHandshakeSession() { public SSLParameters getSSLParameters() { return sslEngine.getSSLParameters(); } - - @Override - public SSLEngineResult wrap(ByteBuffer[] byteBuffers, int i, int i1, ByteBuffer byteBuffer) - throws SSLException { - return null; - } - - @Override - public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers, int i, int i1) - throws SSLException { - return null; - } - - @Override - public Runnable getDelegatedTask() { - return null; - } - - @Override - public void closeInbound() throws SSLException { - - } - - @Override - public boolean isInboundDone() { - return false; - } - - @Override - public void closeOutbound() {} - - @Override - public boolean isOutboundDone() { - return false; - } - - @Override - public String[] getSupportedCipherSuites() { - return new String[0]; - } - - @Override - public String[] getEnabledCipherSuites() { - return new String[0]; - } - - @Override - public void setEnabledCipherSuites(String[] strings) {} - - @Override - public String[] getSupportedProtocols() { - return new String[0]; - } - - @Override - public String[] getEnabledProtocols() { - return new String[0]; - } - - @Override - public void setEnabledProtocols(String[] strings) {} - - @Override - public SSLSession getSession() { - return null; - } - - @Override - public void beginHandshake() throws SSLException {} - - @Override - public HandshakeStatus getHandshakeStatus() { - return null; - } - - @Override - public void setUseClientMode(boolean b) {} - - @Override - public boolean getUseClientMode() { - return false; - } - - @Override - public void setNeedClientAuth(boolean b) {} - - @Override - public boolean getNeedClientAuth() { - return false; - } - - @Override - public void setWantClientAuth(boolean b) {} - - @Override - public boolean getWantClientAuth() { - return false; - } - - @Override - public void setEnableSessionCreation(boolean b) {} - - @Override - public boolean getEnableSessionCreation() { - return false; - } } - static class FakeSslSession implements SSLSession { + static class FakeSslSession extends NoopSslSession { private final String peerHost; FakeSslSession(String peerHost) { this.peerHost = peerHost; } - @Override - public byte[] getId() { - return new byte[0]; - } - - @Override - public SSLSessionContext getSessionContext() { - return null; - } - - @Override - @SuppressWarnings("deprecation") - public javax.security.cert.X509Certificate[] getPeerCertificateChain() { - throw new UnsupportedOperationException("This method is deprecated and marked for removal. " - + "Use the getPeerCertificates() method instead."); - } - - @Override - public long getCreationTime() { - return 0; - } - - @Override - public long getLastAccessedTime() { - return 0; - } - - @Override - public void invalidate() {} - - @Override - public boolean isValid() { - return false; - } - - @Override - public void putValue(String s, Object o) {} - - @Override - public Object getValue(String s) { - return null; - } - - @Override - public void removeValue(String s) {} - - @Override - public String[] getValueNames() { - return new String[0]; - } - - @Override - public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { - return new Certificate[0]; - } - - @Override - public Certificate[] getLocalCertificates() { - return new Certificate[0]; - } - - @Override - public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { - return null; - } - - @Override - public Principal getLocalPrincipal() { - return null; - } - - @Override - public String getCipherSuite() { - return null; - } - - @Override - public String getProtocol() { - return null; - } - @Override public String getPeerHost() { return peerHost; } - - @Override - public int getPeerPort() { - return 0; - } - - @Override - public int getPacketBufferSize() { - return 0; - } - - @Override - public int getApplicationBufferSize() { - return 0; - } } } diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 41722609614..0cc7fae4a81 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -866,7 +866,7 @@ Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.i InsightBuilder insightBuilder = new InsightBuilder(); stream.appendTimeoutInsight(insightBuilder); assertThat(insightBuilder.toString()).contains( - "Status{code=INTERNAL, description=Can't allow authority override in rpc when " + "Status{code=FAILED_PRECONDITION, description=Can't allow authority override in rpc when " + "X509ExtendedTrustManager is not available, cause=null}"); } @@ -891,8 +891,8 @@ Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.i InsightBuilder insightBuilder = new InsightBuilder(); stream.appendTimeoutInsight(insightBuilder); assertThat(insightBuilder.toString()).contains( - "Status{code=INTERNAL, description=Peer hostname verification failed for authority" - + " 'foo.test.google.in'., cause=null}"); + "Status{code=UNAVAILABLE, description=Peer hostname verification failed for authority" + + " 'foo.test.google.in', cause=null}"); } @Test diff --git a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java index 578e47df7a9..e813690a566 100644 --- a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java +++ b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java @@ -1052,14 +1052,14 @@ public void allowedAuthoritiesForTransport_LruCache() throws SSLException, Certi // Fill the cache for (int i = 0; i < 100; i++) { - boolean unused = negotiator.mayBeVerifyAuthority("authority" + i); + Status unused = negotiator.verifyAuthority("authority" + i); } // Should use value from the cache. - boolean unused = negotiator.mayBeVerifyAuthority("authority0"); + Status unused = negotiator.verifyAuthority("authority0"); // Should evict authority0. - unused = negotiator.mayBeVerifyAuthority("authority100"); + unused = negotiator.verifyAuthority("authority100"); // Should call TrustManager as the cached value has been evicted for this authority value. - unused = negotiator.mayBeVerifyAuthority("authority0"); + unused = negotiator.verifyAuthority("authority0"); ArgumentCaptor sslEngineWrapperArgumentCaptor = ArgumentCaptor.forClass(SslEngineWrapper.class); From 862b95b8f7f44ac3357e7effd04c1c8d1d2481d6 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Fri, 13 Dec 2024 20:59:54 +0530 Subject: [PATCH 24/70] Fix warning. --- netty/src/main/java/io/grpc/netty/NettyClientTransport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index f1befbd24e6..e62ddc20abf 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -196,7 +196,7 @@ public ClientStream newStream( } if (callOptions.getAuthority() != null) { Status verificationStatus = negotiator.verifyAuthority(callOptions.getAuthority()); - if (verificationStatus != Status.OK) { + if (!verificationStatus.equals(Status.OK)) { return new FailingClientStream(verificationStatus, tracers); } } From 8902dbdc0e71f9b690bc319c7ff4d0ce784c3740 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Sat, 14 Dec 2024 14:52:32 +0530 Subject: [PATCH 25/70] Address review comments. --- .../io/grpc/internal/CertificateUtils.java | 11 +++----- .../io/grpc/netty/NettyClientTransport.java | 2 +- .../java/io/grpc/netty/NoopSslEngine.java | 2 +- .../io/grpc/netty/ProtocolNegotiator.java | 3 ++- .../io/grpc/netty/ProtocolNegotiators.java | 26 +++++++++---------- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/CertificateUtils.java b/core/src/main/java/io/grpc/internal/CertificateUtils.java index 1d9d9427f05..5b42ad40967 100644 --- a/core/src/main/java/io/grpc/internal/CertificateUtils.java +++ b/core/src/main/java/io/grpc/internal/CertificateUtils.java @@ -26,10 +26,9 @@ import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; -import java.util.Optional; +import java.util.List; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedTrustManager; import javax.security.auth.x500.X500Principal; /** @@ -37,10 +36,9 @@ */ public class CertificateUtils { /** - * Creates a X509ExtendedTrustManager using the provided CA certs if applicable for the - * certificate type. + * Creates a X509TrustManagers using the provided CA certs. */ - public static Optional getX509ExtendedTrustManager(InputStream rootCerts) + public static List getTrustManagers(InputStream rootCerts) throws GeneralSecurityException { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); try { @@ -58,8 +56,7 @@ public static Optional getX509ExtendedTrustManager(InputStream roo TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(ks); - return Arrays.stream(trustManagerFactory.getTrustManagers()) - .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + return Arrays.asList(trustManagerFactory.getTrustManagers()); } private static X509Certificate[] getX509Certificates(InputStream inputStream) diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index e62ddc20abf..6360ee61285 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -196,7 +196,7 @@ public ClientStream newStream( } if (callOptions.getAuthority() != null) { Status verificationStatus = negotiator.verifyAuthority(callOptions.getAuthority()); - if (!verificationStatus.equals(Status.OK)) { + if (!verificationStatus.isOk()) { return new FailingClientStream(verificationStatus, tracers); } } diff --git a/netty/src/main/java/io/grpc/netty/NoopSslEngine.java b/netty/src/main/java/io/grpc/netty/NoopSslEngine.java index b0a5f6936e3..7e14dbf0e79 100644 --- a/netty/src/main/java/io/grpc/netty/NoopSslEngine.java +++ b/netty/src/main/java/io/grpc/netty/NoopSslEngine.java @@ -26,7 +26,7 @@ * A no-op implementation of SslEngine, to facilitate overriding only the required methods in * specific implementations. */ -public class NoopSslEngine extends SSLEngine { +class NoopSslEngine extends SSLEngine { @Override public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int length, ByteBuffer dst) throws SSLException { diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java index 4c7aa653c3c..a91b361b8f7 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java @@ -69,6 +69,7 @@ interface ServerFactory { * Verify the authority against peer if applicable depending on the transport credential type. */ default Status verifyAuthority(String authority) { - return Status.UNAVAILABLE; + return Status.UNAVAILABLE.withDescription("Per-rpc authority verification not implemented by " + + "the protocol negotiator"); } } diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 995e82a5a6b..2c52a7f27a7 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import static io.grpc.internal.CertificateUtils.getX509ExtendedTrustManager; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; @@ -42,6 +41,7 @@ import io.grpc.Status; import io.grpc.TlsChannelCredentials; import io.grpc.TlsServerCredentials; +import io.grpc.internal.CertificateUtils; import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; @@ -79,6 +79,7 @@ import java.util.Arrays; import java.util.EnumSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -130,27 +131,24 @@ public static FromChannelCredentialsResult from(ChannelCredentials creds) { new ByteArrayInputStream(tlsCreds.getPrivateKey()), tlsCreds.getPrivateKeyPassword()); } - Optional x509ExtendedTrustManager; try { + List trustManagers; if (tlsCreds.getTrustManagers() != null) { - builder.trustManager(new FixedTrustManagerFactory(tlsCreds.getTrustManagers())); - x509ExtendedTrustManager = tlsCreds.getTrustManagers().stream().filter( - trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + trustManagers = tlsCreds.getTrustManagers(); } else if (tlsCreds.getRootCertificates() != null) { - builder.trustManager(new ByteArrayInputStream(tlsCreds.getRootCertificates())); - x509ExtendedTrustManager = getX509ExtendedTrustManager(new ByteArrayInputStream( - tlsCreds.getRootCertificates())); + trustManagers = CertificateUtils.getTrustManagers( + new ByteArrayInputStream(tlsCreds.getRootCertificates())); } else { // else use system default TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); tmf.init((KeyStore) null); - x509ExtendedTrustManager = Arrays.stream(tmf.getTrustManagers()) - .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + trustManagers = Arrays.asList(tmf.getTrustManagers()); } + builder.trustManager(new FixedTrustManagerFactory(trustManagers)); + Optional x509ExtendedTrustManager = trustManagers.stream().filter( + trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); return FromChannelCredentialsResult.negotiator(tlsClientFactory(builder.build(), - x509ExtendedTrustManager.isPresent() - ? (X509ExtendedTrustManager) x509ExtendedTrustManager.get() - : null)); + (X509ExtendedTrustManager) x509ExtendedTrustManager.orElse(null))); } catch (SSLException | GeneralSecurityException ex) { log.log(Level.FINE, "Exception building SslContext", ex); return FromChannelCredentialsResult.error( @@ -1233,7 +1231,7 @@ public SSLParameters getSSLParameters() { } } - static class FakeSslSession extends NoopSslSession { + static final class FakeSslSession extends NoopSslSession { private final String peerHost; FakeSslSession(String peerHost) { From 01b0eb29dde6f401a3a56b6c0b54abe0199c41f6 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Sat, 14 Dec 2024 15:07:25 +0530 Subject: [PATCH 26/70] Address review comments. --- netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 2c52a7f27a7..4ef9c23d1ae 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -632,7 +632,7 @@ public synchronized Status verifyAuthority(@Nonnull String authority) { } catch (SSLPeerUnverifiedException | CertificateException e) { peerVerificationStatus = Status.UNAVAILABLE.withDescription( String.format("Peer hostname verification failed for authority '%s'", - authority)); + authority)).withCause(e); } peerVerificationResults.put(authority, peerVerificationStatus); return peerVerificationStatus; From 60bfa0618f7f64ef3ca064c4948f7a75ba7b7412 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Sat, 14 Dec 2024 19:26:57 +0530 Subject: [PATCH 27/70] In progress changes. --- .../java/io/grpc/ManagedChannelRegistry.java | 56 +++++++++---------- .../io/grpc/okhttp/OkHttpClientTransport.java | 14 +++-- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/api/src/main/java/io/grpc/ManagedChannelRegistry.java b/api/src/main/java/io/grpc/ManagedChannelRegistry.java index d2ed9e496ae..d6fba4beca6 100644 --- a/api/src/main/java/io/grpc/ManagedChannelRegistry.java +++ b/api/src/main/java/io/grpc/ManagedChannelRegistry.java @@ -99,10 +99,10 @@ public int compare(ManagedChannelProvider o1, ManagedChannelProvider o2) { public static synchronized ManagedChannelRegistry getDefaultRegistry() { if (instance == null) { List providerList = ServiceProviders.loadAll( - ManagedChannelProvider.class, - getHardCodedClasses(), - ManagedChannelProvider.class.getClassLoader(), - new ManagedChannelPriorityAccessor()); + ManagedChannelProvider.class, + getHardCodedClasses(), + ManagedChannelProvider.class.getClassLoader(), + new ManagedChannelPriorityAccessor()); instance = new ManagedChannelRegistry(); for (ManagedChannelProvider provider : providerList) { logger.fine("Service loader found " + provider); @@ -157,7 +157,7 @@ ManagedChannelBuilder newChannelBuilder(String target, ChannelCredentials cre @VisibleForTesting ManagedChannelBuilder newChannelBuilder(NameResolverRegistry nameResolverRegistry, - String target, ChannelCredentials creds) { + String target, ChannelCredentials creds) { NameResolverProvider nameResolverProvider = null; try { URI uri = new URI(target); @@ -167,35 +167,35 @@ ManagedChannelBuilder newChannelBuilder(NameResolverRegistry nameResolverRegi } if (nameResolverProvider == null) { nameResolverProvider = nameResolverRegistry.getProviderForScheme( - nameResolverRegistry.getDefaultScheme()); + nameResolverRegistry.getDefaultScheme()); } Collection> nameResolverSocketAddressTypes - = (nameResolverProvider != null) - ? nameResolverProvider.getProducedSocketAddressTypes() : - Collections.emptySet(); + = (nameResolverProvider != null) + ? nameResolverProvider.getProducedSocketAddressTypes() : + Collections.emptySet(); List providers = providers(); if (providers.isEmpty()) { throw new ProviderNotFoundException("No functional channel service provider found. " - + "Try adding a dependency on the grpc-okhttp, grpc-netty, or grpc-netty-shaded " - + "artifact"); + + "Try adding a dependency on the grpc-okhttp, grpc-netty, or grpc-netty-shaded " + + "artifact"); } StringBuilder error = new StringBuilder(); - for (ManagedChannelProvider provider : providers()) { - Collection> channelProviderSocketAddressTypes - = provider.getSupportedSocketAddressTypes(); - if (!channelProviderSocketAddressTypes.containsAll(nameResolverSocketAddressTypes)) { - error.append("; "); - error.append(provider.getClass().getName()); - error.append(": does not support 1 or more of "); - error.append(Arrays.toString(nameResolverSocketAddressTypes.toArray())); - continue; - } - ManagedChannelProvider.NewChannelBuilderResult result - = provider.newChannelBuilder(target, creds); - if (result.getChannelBuilder() != null) { - return result.getChannelBuilder(); - } + for (ManagedChannelProvider provider : providers()) { + Collection> channelProviderSocketAddressTypes + = provider.getSupportedSocketAddressTypes(); + if (!channelProviderSocketAddressTypes.containsAll(nameResolverSocketAddressTypes)) { + error.append("; "); + error.append(provider.getClass().getName()); + error.append(": does not support 1 or more of "); + error.append(Arrays.toString(nameResolverSocketAddressTypes.toArray())); + continue; + } + ManagedChannelProvider.NewChannelBuilderResult result + = provider.newChannelBuilder(target, creds); + if (result.getChannelBuilder() != null) { + return result.getChannelBuilder(); + } error.append("; "); error.append(provider.getClass().getName()); error.append(": "); @@ -205,7 +205,7 @@ ManagedChannelBuilder newChannelBuilder(NameResolverRegistry nameResolverRegi } private static final class ManagedChannelPriorityAccessor - implements ServiceProviders.PriorityAccessor { + implements ServiceProviders.PriorityAccessor { @Override public boolean isAvailable(ManagedChannelProvider provider) { return provider.isAvailable(); @@ -225,4 +225,4 @@ public ProviderNotFoundException(String msg) { super(msg); } } -} +} \ No newline at end of file diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 173fb6d60db..0e856566cfd 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -111,7 +111,6 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import javax.net.SocketFactory; -import javax.net.ssl.ExtendedSSLSession; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLParameters; @@ -232,7 +231,7 @@ private static Map buildErrorCodeToStatusMap() { private final boolean useGetForSafeMethods; @GuardedBy("lock") private final TransportTracer transportTracer; - private final ConcurrentHashMap authoritiesAllowedForPeer = + private final ConcurrentHashMap authorityVerificationStatuses = new ConcurrentHashMap<>(); @GuardedBy("lock") @@ -433,9 +432,12 @@ public ClientStream newStream( StatsTraceContext.newClientContext(tracers, getAttributes(), headers); if (socket instanceof SSLSocket && callOptions.getAuthority() != null && channelCredentials != null && channelCredentials instanceof TlsChannelCredentials) { + if (hostnameVerifier != null) { + hostnameVerifier.verify(callOptions.getAuthority(), ((SSLSocket) socket).getSession()); + } boolean isAuthorityValid; - if (authoritiesAllowedForPeer.containsKey(callOptions.getAuthority())) { - isAuthorityValid = authoritiesAllowedForPeer.get(callOptions.getAuthority()); + if (authorityVerificationStatuses.containsKey(callOptions.getAuthority())) { + isAuthorityValid = authorityVerificationStatuses.get(callOptions.getAuthority()); } else { Optional x509ExtendedTrustManager; try { @@ -461,10 +463,10 @@ public ClientStream newStream( ((X509ExtendedTrustManager) x509ExtendedTrustManager.get()).checkServerTrusted( x509PeerCertificates, "RSA", new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); - authoritiesAllowedForPeer.put(callOptions.getAuthority(), true); + authorityVerificationStatuses.put(callOptions.getAuthority(), true); } catch (SSLPeerUnverifiedException | CertificateException e) { log.log(Level.FINE, "Failure in verifying authority against peer", e); - authoritiesAllowedForPeer.put(callOptions.getAuthority(), false); + authorityVerificationStatuses.put(callOptions.getAuthority(), false); return new FailingClientStream(Status.INTERNAL.withDescription( "Failure in verifying authority against peer"), tracers); From 8dd8749a9ac0f7909d4f54a07ea62236d30b931f Mon Sep 17 00:00:00 2001 From: deadEternally Date: Sat, 14 Dec 2024 19:34:30 +0530 Subject: [PATCH 28/70] Remove duplicate definitions of createTrustManager. --- .../io/grpc/internal/CertificateUtils.java | 8 +-- .../io/grpc/netty/ProtocolNegotiators.java | 4 +- .../io/grpc/okhttp/OkHttpChannelBuilder.java | 69 +++---------------- 3 files changed, 14 insertions(+), 67 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/CertificateUtils.java b/core/src/main/java/io/grpc/internal/CertificateUtils.java index 5b42ad40967..7efd16eaf27 100644 --- a/core/src/main/java/io/grpc/internal/CertificateUtils.java +++ b/core/src/main/java/io/grpc/internal/CertificateUtils.java @@ -24,9 +24,7 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.util.Arrays; import java.util.Collection; -import java.util.List; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.security.auth.x500.X500Principal; @@ -36,9 +34,9 @@ */ public class CertificateUtils { /** - * Creates a X509TrustManagers using the provided CA certs. + * Creates X509TrustManagers using the provided CA certs. */ - public static List getTrustManagers(InputStream rootCerts) + public static TrustManager[] createTrustManager(InputStream rootCerts) throws GeneralSecurityException { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); try { @@ -56,7 +54,7 @@ public static List getTrustManagers(InputStream rootCerts) TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(ks); - return Arrays.asList(trustManagerFactory.getTrustManagers()); + return trustManagerFactory.getTrustManagers(); } private static X509Certificate[] getX509Certificates(InputStream inputStream) diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 4ef9c23d1ae..de935608839 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -136,8 +136,8 @@ public static FromChannelCredentialsResult from(ChannelCredentials creds) { if (tlsCreds.getTrustManagers() != null) { trustManagers = tlsCreds.getTrustManagers(); } else if (tlsCreds.getRootCertificates() != null) { - trustManagers = CertificateUtils.getTrustManagers( - new ByteArrayInputStream(tlsCreds.getRootCertificates())); + trustManagers = Arrays.asList(CertificateUtils.createTrustManager( + new ByteArrayInputStream(tlsCreds.getRootCertificates()))); } else { // else use system default TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java index 15508110344..51d7551ccf2 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java @@ -16,37 +16,13 @@ package io.grpc.okhttp; -import static com.google.common.base.Preconditions.checkNotNull; -import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS; -import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED; - import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; -import io.grpc.CallCredentials; -import io.grpc.ChannelCredentials; -import io.grpc.ChannelLogger; -import io.grpc.ChoiceChannelCredentials; -import io.grpc.CompositeCallCredentials; -import io.grpc.CompositeChannelCredentials; -import io.grpc.ExperimentalApi; -import io.grpc.ForwardingChannelBuilder2; -import io.grpc.InsecureChannelCredentials; -import io.grpc.Internal; -import io.grpc.ManagedChannelBuilder; -import io.grpc.TlsChannelCredentials; -import io.grpc.internal.AtomicBackoff; -import io.grpc.internal.ClientTransportFactory; -import io.grpc.internal.ConnectionClientTransport; -import io.grpc.internal.FixedObjectPool; -import io.grpc.internal.GrpcUtil; -import io.grpc.internal.KeepAliveManager; -import io.grpc.internal.ManagedChannelImplBuilder; +import io.grpc.*; +import io.grpc.internal.*; import io.grpc.internal.ManagedChannelImplBuilder.ChannelBuilderDefaultPortProvider; import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder; -import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourceHolder.Resource; -import io.grpc.internal.SharedResourcePool; -import io.grpc.internal.TransportTracer; import io.grpc.okhttp.internal.CipherSuite; import io.grpc.okhttp.internal.ConnectionSpec; import io.grpc.okhttp.internal.Platform; @@ -65,24 +41,17 @@ import java.util.Collections; import java.util.EnumSet; import java.util.Set; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; import javax.net.SocketFactory; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.security.auth.x500.X500Principal; +import javax.net.ssl.*; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS; +import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED; /** Convenience class for building channels with the OkHttp transport. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1785") @@ -705,32 +674,12 @@ static KeyManager[] createKeyManager(InputStream certChain, InputStream privateK static TrustManager[] createTrustManager(byte[] rootCerts) throws GeneralSecurityException { InputStream rootCertsStream = new ByteArrayInputStream(rootCerts); try { - return createTrustManager(rootCertsStream); + return io.grpc.internal.CertificateUtils.createTrustManager(rootCertsStream); } finally { GrpcUtil.closeQuietly(rootCertsStream); } } - static TrustManager[] createTrustManager(InputStream rootCerts) throws GeneralSecurityException { - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - try { - ks.load(null, null); - } catch (IOException ex) { - // Shouldn't really happen, as we're not loading any data. - throw new GeneralSecurityException(ex); - } - X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); - for (X509Certificate cert : certs) { - X500Principal principal = cert.getSubjectX500Principal(); - ks.setCertificateEntry(principal.getName("RFC2253"), cert); - } - - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(ks); - return trustManagerFactory.getTrustManagers(); - } - static Collection> getSupportedSocketAddressTypes() { return Collections.singleton(InetSocketAddress.class); } From 23a0822e64ded3b083db487b55b64bda646dede0 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Sat, 14 Dec 2024 20:35:38 +0530 Subject: [PATCH 29/70] in-progress changes. --- .../io/grpc/internal/CertificateUtils.java | 79 ++++++++++++ .../io/grpc/okhttp/OkHttpChannelBuilder.java | 77 ++---------- .../io/grpc/okhttp/OkHttpClientTransport.java | 117 +++++++++--------- .../java/io/grpc/util/CertificateUtils.java | 40 +----- 4 files changed, 145 insertions(+), 168 deletions(-) create mode 100644 core/src/main/java/io/grpc/internal/CertificateUtils.java diff --git a/core/src/main/java/io/grpc/internal/CertificateUtils.java b/core/src/main/java/io/grpc/internal/CertificateUtils.java new file mode 100644 index 00000000000..85b687edcc7 --- /dev/null +++ b/core/src/main/java/io/grpc/internal/CertificateUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright 2024 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.internal; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.security.auth.x500.X500Principal; + +/** + * Contains certificate/key PEM file utility method(s) for internal usage. + */ +public class CertificateUtils { + /** + * Creates X509TrustManagers using the provided CA certs. + */ + public static TrustManager[] createTrustManager(byte[] rootCerts) throws GeneralSecurityException { + InputStream rootCertsStream = new ByteArrayInputStream(rootCerts); + try { + return io.grpc.internal.CertificateUtils.createTrustManager(rootCertsStream); + } finally { + GrpcUtil.closeQuietly(rootCertsStream); + } + } + + /** + * Creates X509TrustManagers using the provided input stream of CA certs. + */ + public static TrustManager[] createTrustManager(InputStream rootCerts) + throws GeneralSecurityException { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + try { + ks.load(null, null); + } catch (IOException ex) { + // Shouldn't really happen, as we're not loading any data. + throw new GeneralSecurityException(ex); + } + X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); + for (X509Certificate cert : certs) { + X500Principal principal = cert.getSubjectX500Principal(); + ks.setCertificateEntry(principal.getName("RFC2253"), cert); + } + + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(ks); + return trustManagerFactory.getTrustManagers(); + } + + private static X509Certificate[] getX509Certificates(InputStream inputStream) + throws CertificateException { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + Collection certs = factory.generateCertificates(inputStream); + return certs.toArray(new X509Certificate[0]); + } +} diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java index fff5e47ff77..3715a010739 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java @@ -16,37 +16,13 @@ package io.grpc.okhttp; -import static com.google.common.base.Preconditions.checkNotNull; -import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS; -import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED; - import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; -import io.grpc.CallCredentials; -import io.grpc.ChannelCredentials; -import io.grpc.ChannelLogger; -import io.grpc.ChoiceChannelCredentials; -import io.grpc.CompositeCallCredentials; -import io.grpc.CompositeChannelCredentials; -import io.grpc.ExperimentalApi; -import io.grpc.ForwardingChannelBuilder2; -import io.grpc.InsecureChannelCredentials; -import io.grpc.Internal; -import io.grpc.ManagedChannelBuilder; -import io.grpc.TlsChannelCredentials; -import io.grpc.internal.AtomicBackoff; -import io.grpc.internal.ClientTransportFactory; -import io.grpc.internal.ConnectionClientTransport; -import io.grpc.internal.FixedObjectPool; -import io.grpc.internal.GrpcUtil; -import io.grpc.internal.KeepAliveManager; -import io.grpc.internal.ManagedChannelImplBuilder; +import io.grpc.*; +import io.grpc.internal.*; import io.grpc.internal.ManagedChannelImplBuilder.ChannelBuilderDefaultPortProvider; import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder; -import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourceHolder.Resource; -import io.grpc.internal.SharedResourcePool; -import io.grpc.internal.TransportTracer; import io.grpc.internal.TransportTracer.Factory; import io.grpc.okhttp.internal.CipherSuite; import io.grpc.okhttp.internal.ConnectionSpec; @@ -66,24 +42,18 @@ import java.util.Collections; import java.util.EnumSet; import java.util.Set; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; import javax.net.SocketFactory; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.security.auth.x500.X500Principal; +import javax.net.ssl.*; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.internal.CertificateUtils.createTrustManager; +import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS; +import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED; /** Convenience class for building channels with the OkHttp transport. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1785") @@ -707,35 +677,6 @@ static KeyManager[] createKeyManager(InputStream certChain, InputStream privateK return keyManagerFactory.getKeyManagers(); } - static TrustManager[] createTrustManager(byte[] rootCerts) throws GeneralSecurityException { - InputStream rootCertsStream = new ByteArrayInputStream(rootCerts); - try { - return createTrustManager(rootCertsStream); - } finally { - GrpcUtil.closeQuietly(rootCertsStream); - } - } - - static TrustManager[] createTrustManager(InputStream rootCerts) throws GeneralSecurityException { - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - try { - ks.load(null, null); - } catch (IOException ex) { - // Shouldn't really happen, as we're not loading any data. - throw new GeneralSecurityException(ex); - } - X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); - for (X509Certificate cert : certs) { - X500Principal principal = cert.getSubjectX500Principal(); - ks.setCertificateEntry(principal.getName("RFC2253"), cert); - } - - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(ks); - return trustManagerFactory.getTrustManagers(); - } - static Collection> getSupportedSocketAddressTypes() { return Collections.singleton(InetSocketAddress.class); } diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 0e856566cfd..d779a8c4f86 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -72,9 +72,7 @@ import io.grpc.okhttp.internal.framed.Variant; import io.grpc.okhttp.internal.proxy.HttpUrl; import io.grpc.okhttp.internal.proxy.Request; -import io.grpc.util.CertificateUtils; import io.perfmark.PerfMark; -import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; import java.net.InetSocketAddress; @@ -86,20 +84,8 @@ import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.Collections; -import java.util.Deque; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.Random; +import java.util.*; import java.util.concurrent.BrokenBarrierException; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.Executor; @@ -231,8 +217,13 @@ private static Map buildErrorCodeToStatusMap() { private final boolean useGetForSafeMethods; @GuardedBy("lock") private final TransportTracer transportTracer; - private final ConcurrentHashMap authorityVerificationStatuses = - new ConcurrentHashMap<>(); + private final LinkedHashMap peerVerificationResults = + new LinkedHashMap() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > 100; + } + }; @GuardedBy("lock") private final InUseStateAggregator inUseState = @@ -432,45 +423,51 @@ public ClientStream newStream( StatsTraceContext.newClientContext(tracers, getAttributes(), headers); if (socket instanceof SSLSocket && callOptions.getAuthority() != null && channelCredentials != null && channelCredentials instanceof TlsChannelCredentials) { - if (hostnameVerifier != null) { - hostnameVerifier.verify(callOptions.getAuthority(), ((SSLSocket) socket).getSession()); - } - boolean isAuthorityValid; - if (authorityVerificationStatuses.containsKey(callOptions.getAuthority())) { - isAuthorityValid = authorityVerificationStatuses.get(callOptions.getAuthority()); + Status peerVerificationStatus; + if (peerVerificationResults.containsKey(callOptions.getAuthority())) { + peerVerificationStatus = peerVerificationResults.get(callOptions.getAuthority()); } else { - Optional x509ExtendedTrustManager; - try { - x509ExtendedTrustManager = getX509ExtendedTrustManager( - (TlsChannelCredentials) channelCredentials); - } catch (GeneralSecurityException e) { - log.log(Level.FINE, "Failure getting X509ExtendedTrustManager from TlsCredentials", e); - return new FailingClientStream(Status.INTERNAL.withDescription( - "Failure getting X509ExtendedTrustManager from TlsCredentials"), - tracers); - } - if (!x509ExtendedTrustManager.isPresent()) { - return new FailingClientStream(Status.INTERNAL.withDescription( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not " - + "available"), tracers); - } - try { - Certificate[] peerCertificates = sslSession.getPeerCertificates(); - X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; - for (int i = 0; i < peerCertificates.length; i++) { - x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; + if (hostnameVerifier != null && + !hostnameVerifier.verify(callOptions.getAuthority(), + ((SSLSocket) socket).getSession())) { + peerVerificationStatus = Status.UNAVAILABLE.withDescription( + String.format("HostNameVerifier verification failed for authority '%s'.", + callOptions.getAuthority())); + } else { + Optional x509ExtendedTrustManager; + try { + x509ExtendedTrustManager = getX509ExtendedTrustManager( + (TlsChannelCredentials) channelCredentials); + } catch (GeneralSecurityException e) { + return new FailingClientStream(Status.UNAVAILABLE.withDescription( + "Failure getting X509ExtendedTrustManager from TlsCredentials").withCause(e), + tracers); + } + if (!x509ExtendedTrustManager.isPresent()) { + return new FailingClientStream(Status.UNAVAILABLE.withDescription( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not " + + "available"), tracers); + } + try { + Certificate[] peerCertificates = sslSession.getPeerCertificates(); + X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; + for (int i = 0; i < peerCertificates.length; i++) { + x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; + } + ((X509ExtendedTrustManager) x509ExtendedTrustManager.get()).checkServerTrusted( + x509PeerCertificates, "RSA", + new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); + peerVerificationStatus = Status.OK; + } catch (SSLPeerUnverifiedException | CertificateException e) { + peerVerificationStatus = Status.INTERNAL.withDescription( + String.format("Failure in verifying authority '%s' against peer", + callOptions.getAuthority())).withCause(e); } - ((X509ExtendedTrustManager) x509ExtendedTrustManager.get()).checkServerTrusted( - x509PeerCertificates, "RSA", - new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); - authorityVerificationStatuses.put(callOptions.getAuthority(), true); - } catch (SSLPeerUnverifiedException | CertificateException e) { - log.log(Level.FINE, "Failure in verifying authority against peer", e); - authorityVerificationStatuses.put(callOptions.getAuthority(), false); - return new FailingClientStream(Status.INTERNAL.withDescription( - "Failure in verifying authority against peer"), - tracers); } + peerVerificationResults.put(callOptions.getAuthority(), peerVerificationStatus); + } + if (!peerVerificationStatus.isOk()) { + return new FailingClientStream(peerVerificationStatus, tracers); } } // FIXME: it is likely wrong to pass the transportTracer here as it'll exit the lock's scope @@ -495,21 +492,19 @@ public ClientStream newStream( private Optional getX509ExtendedTrustManager(TlsChannelCredentials tlsCreds) throws GeneralSecurityException { - Optional x509ExtendedTrustManager; + TrustManager[] tm = null; + // Using the same way of creating TrustManager from {@link OkHttpChannelBuilder#sslSocketFactoryFrom}. if (tlsCreds.getTrustManagers() != null) { - x509ExtendedTrustManager = tlsCreds.getTrustManagers().stream().filter( - trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + tm = tlsCreds.getTrustManagers().toArray(new TrustManager[0]); } else if (tlsCreds.getRootCertificates() != null) { - x509ExtendedTrustManager = CertificateUtils.getX509ExtendedTrustManager( - new ByteArrayInputStream(tlsCreds.getRootCertificates())); + tm = io.grpc.internal.CertificateUtils.createTrustManager(tlsCreds.getRootCertificates()); } else { // else use system default TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); tmf.init((KeyStore) null); - x509ExtendedTrustManager = Arrays.stream(tmf.getTrustManagers()) - .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + tm = tmf.getTrustManagers(); } - return x509ExtendedTrustManager; + return Arrays.stream(tm).filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); } @GuardedBy("lock") diff --git a/util/src/main/java/io/grpc/util/CertificateUtils.java b/util/src/main/java/io/grpc/util/CertificateUtils.java index 400c6a54a5a..a91719b1b41 100644 --- a/util/src/main/java/io/grpc/util/CertificateUtils.java +++ b/util/src/main/java/io/grpc/util/CertificateUtils.java @@ -18,14 +18,8 @@ import com.google.common.io.BaseEncoding; import io.grpc.ExperimentalApi; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.security.GeneralSecurityException; +import java.io.*; import java.security.KeyFactory; -import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; @@ -34,13 +28,7 @@ import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Arrays; import java.util.Collection; -import java.util.Optional; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedTrustManager; -import javax.security.auth.x500.X500Principal; /** * Contains certificate/key PEM file utility method(s). @@ -99,31 +87,5 @@ public static PrivateKey getPrivateKey(InputStream inputStream) } } } - - /** - * Creates a X509ExtendedTrustManager using the provided CA certs if applicable for the - * certificate type. - */ - public static Optional getX509ExtendedTrustManager(InputStream rootCerts) - throws GeneralSecurityException { - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - try { - ks.load(null, null); - } catch (IOException ex) { - // Shouldn't really happen, as we're not loading any data. - throw new GeneralSecurityException(ex); - } - X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); - for (X509Certificate cert : certs) { - X500Principal principal = cert.getSubjectX500Principal(); - ks.setCertificateEntry(principal.getName("RFC2253"), cert); - } - - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(ks); - return Arrays.stream(trustManagerFactory.getTrustManagers()) - .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); - } } From 0a9124c64cafb1c059909dcbd2d1247ac6e68aba Mon Sep 17 00:00:00 2001 From: Kannan J Date: Sat, 14 Dec 2024 15:57:17 +0000 Subject: [PATCH 30/70] in-progress changes. --- core/src/main/java/io/grpc/internal/CertificateUtils.java | 3 ++- okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/CertificateUtils.java b/core/src/main/java/io/grpc/internal/CertificateUtils.java index 85b687edcc7..11abe7610ec 100644 --- a/core/src/main/java/io/grpc/internal/CertificateUtils.java +++ b/core/src/main/java/io/grpc/internal/CertificateUtils.java @@ -37,7 +37,8 @@ public class CertificateUtils { /** * Creates X509TrustManagers using the provided CA certs. */ - public static TrustManager[] createTrustManager(byte[] rootCerts) throws GeneralSecurityException { + public static TrustManager[] createTrustManager(byte[] rootCerts) + throws GeneralSecurityException { InputStream rootCertsStream = new ByteArrayInputStream(rootCerts); try { return io.grpc.internal.CertificateUtils.createTrustManager(rootCertsStream); diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java index 068474d70bc..8daeed42a8c 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java @@ -17,6 +17,7 @@ package io.grpc.okhttp; import static com.google.common.base.Preconditions.checkArgument; +import static io.grpc.internal.CertificateUtils.createTrustManager; import com.google.common.base.Preconditions; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -425,7 +426,7 @@ static HandshakerSocketFactoryResult handshakerSocketFactoryFrom(ServerCredentia tm = tlsCreds.getTrustManagers().toArray(new TrustManager[0]); } else if (tlsCreds.getRootCertificates() != null) { try { - tm = OkHttpChannelBuilder.createTrustManager(tlsCreds.getRootCertificates()); + tm = createTrustManager(tlsCreds.getRootCertificates()); } catch (GeneralSecurityException gse) { log.log(Level.FINE, "Exception loading root certificates from credential", gse); return HandshakerSocketFactoryResult.error( From f9305e7c2d4d9ef8bd6d9d9e6a5f0234126aab80 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Sat, 14 Dec 2024 21:37:14 +0530 Subject: [PATCH 31/70] in-progress changes. --- .../java/io/grpc/okhttp/OkHttpChannelBuilderTest.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java index 3670cd057c1..7d166a8d227 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java @@ -34,11 +34,8 @@ import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; import io.grpc.TlsChannelCredentials; -import io.grpc.internal.ClientTransportFactory; +import io.grpc.internal.*; import io.grpc.internal.ClientTransportFactory.SwapChannelCredentialsResult; -import io.grpc.internal.FakeClock; -import io.grpc.internal.GrpcUtil; -import io.grpc.internal.SharedResourceHolder; import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.TlsTesting; import java.io.InputStream; @@ -212,7 +209,7 @@ public void sslSocketFactoryFrom_tls_mtls() throws Exception { TrustManager[] trustManagers; try (InputStream ca = TlsTesting.loadCert("ca.pem")) { - trustManagers = OkHttpChannelBuilder.createTrustManager(ca); + trustManagers = CertificateUtils.createTrustManager(ca); } SSLContext serverContext = SSLContext.getInstance("TLS"); @@ -257,7 +254,7 @@ public void sslSocketFactoryFrom_tls_mtls_keyFile() throws Exception { InputStream ca = TlsTesting.loadCert("ca.pem")) { serverContext.init( OkHttpChannelBuilder.createKeyManager(server1Chain, server1Key), - OkHttpChannelBuilder.createTrustManager(ca), + CertificateUtils.createTrustManager(ca), null); } final SSLServerSocket serverListenSocket = From 4af94eceeae6e967a6565b6df7c3e5686bb90279 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Sat, 14 Dec 2024 21:42:10 +0530 Subject: [PATCH 32/70] in-progress changes. --- .../test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java index 7d166a8d227..c86e80656e3 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java @@ -34,8 +34,12 @@ import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; import io.grpc.TlsChannelCredentials; -import io.grpc.internal.*; +import io.grpc.internal.CertificateUtils; +import io.grpc.internal.ClientTransportFactory; import io.grpc.internal.ClientTransportFactory.SwapChannelCredentialsResult; +import io.grpc.internal.FakeClock; +import io.grpc.internal.GrpcUtil; +import io.grpc.internal.SharedResourceHolder; import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.TlsTesting; import java.io.InputStream; From aa59965dea0e6b9f22276185b5b1afdc19509fa1 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Sun, 15 Dec 2024 18:35:48 +0530 Subject: [PATCH 33/70] Unit tests and using HostnameVerifier in per-rpc. --- .../io/grpc/okhttp/OkHttpChannelBuilder.java | 50 +++- .../io/grpc/okhttp/OkHttpClientTransport.java | 91 ++++---- .../okhttp/OkHttpClientTransportTest.java | 214 +++++++++++++++++- .../src/test/java/io/grpc/okhttp/TlsTest.java | 157 +++++++++++++ .../java/io/grpc/util/CertificateUtils.java | 6 +- 5 files changed, 461 insertions(+), 57 deletions(-) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java index 3715a010739..dcffd7daad1 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java @@ -16,14 +16,38 @@ package io.grpc.okhttp; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.internal.CertificateUtils.createTrustManager; +import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS; +import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED; + import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; -import io.grpc.*; -import io.grpc.internal.*; +import io.grpc.CallCredentials; +import io.grpc.ChannelCredentials; +import io.grpc.ChannelLogger; +import io.grpc.ChoiceChannelCredentials; +import io.grpc.CompositeCallCredentials; +import io.grpc.CompositeChannelCredentials; +import io.grpc.ExperimentalApi; +import io.grpc.ForwardingChannelBuilder2; +import io.grpc.InsecureChannelCredentials; +import io.grpc.Internal; +import io.grpc.ManagedChannelBuilder; +import io.grpc.TlsChannelCredentials; +import io.grpc.internal.AtomicBackoff; +import io.grpc.internal.ClientTransportFactory; +import io.grpc.internal.ConnectionClientTransport; +import io.grpc.internal.FixedObjectPool; +import io.grpc.internal.GrpcUtil; +import io.grpc.internal.KeepAliveManager; +import io.grpc.internal.ManagedChannelImplBuilder; import io.grpc.internal.ManagedChannelImplBuilder.ChannelBuilderDefaultPortProvider; import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder; +import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourceHolder.Resource; -import io.grpc.internal.TransportTracer.Factory; +import io.grpc.internal.SharedResourcePool; +import io.grpc.internal.TransportTracer; import io.grpc.okhttp.internal.CipherSuite; import io.grpc.okhttp.internal.ConnectionSpec; import io.grpc.okhttp.internal.Platform; @@ -42,18 +66,22 @@ import java.util.Collections; import java.util.EnumSet; import java.util.Set; -import java.util.concurrent.*; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; import javax.net.SocketFactory; -import javax.net.ssl.*; - -import static com.google.common.base.Preconditions.checkNotNull; -import static io.grpc.internal.CertificateUtils.createTrustManager; -import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS; -import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; /** Convenience class for building channels with the OkHttp transport. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1785") @@ -762,7 +790,7 @@ private OkHttpTransportFactory( int flowControlWindow, boolean keepAliveWithoutCalls, int maxInboundMetadataSize, - Factory transportTracerFactory, + TransportTracer.Factory transportTracerFactory, boolean useGetForSafeMethods, ChannelCredentials channelCredentials) { this.executorPool = executorPool; diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index d779a8c4f86..9eddf5468ae 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -84,7 +84,19 @@ import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; @@ -421,48 +433,47 @@ public ClientStream newStream( Preconditions.checkNotNull(headers, "headers"); StatsTraceContext statsTraceContext = StatsTraceContext.newClientContext(tracers, getAttributes(), headers); + if (hostnameVerifier != null && socket instanceof SSLSocket + && !hostnameVerifier.verify(callOptions.getAuthority(), + ((SSLSocket) socket).getSession())) { + return new FailingClientStream(Status.UNAVAILABLE.withDescription( + String.format("HostNameVerifier verification failed for authority '%s'", + callOptions.getAuthority())), tracers); + } if (socket instanceof SSLSocket && callOptions.getAuthority() != null && channelCredentials != null && channelCredentials instanceof TlsChannelCredentials) { Status peerVerificationStatus; if (peerVerificationResults.containsKey(callOptions.getAuthority())) { peerVerificationStatus = peerVerificationResults.get(callOptions.getAuthority()); } else { - if (hostnameVerifier != null && - !hostnameVerifier.verify(callOptions.getAuthority(), - ((SSLSocket) socket).getSession())) { - peerVerificationStatus = Status.UNAVAILABLE.withDescription( - String.format("HostNameVerifier verification failed for authority '%s'.", - callOptions.getAuthority())); - } else { - Optional x509ExtendedTrustManager; - try { - x509ExtendedTrustManager = getX509ExtendedTrustManager( - (TlsChannelCredentials) channelCredentials); - } catch (GeneralSecurityException e) { - return new FailingClientStream(Status.UNAVAILABLE.withDescription( - "Failure getting X509ExtendedTrustManager from TlsCredentials").withCause(e), - tracers); - } - if (!x509ExtendedTrustManager.isPresent()) { - return new FailingClientStream(Status.UNAVAILABLE.withDescription( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not " - + "available"), tracers); - } - try { - Certificate[] peerCertificates = sslSession.getPeerCertificates(); - X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; - for (int i = 0; i < peerCertificates.length; i++) { - x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; - } - ((X509ExtendedTrustManager) x509ExtendedTrustManager.get()).checkServerTrusted( - x509PeerCertificates, "RSA", - new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); - peerVerificationStatus = Status.OK; - } catch (SSLPeerUnverifiedException | CertificateException e) { - peerVerificationStatus = Status.INTERNAL.withDescription( - String.format("Failure in verifying authority '%s' against peer", - callOptions.getAuthority())).withCause(e); + Optional x509ExtendedTrustManager; + try { + x509ExtendedTrustManager = getX509ExtendedTrustManager( + (TlsChannelCredentials) channelCredentials); + } catch (GeneralSecurityException e) { + return new FailingClientStream(Status.UNAVAILABLE.withDescription( + "Failure getting X509ExtendedTrustManager from TlsCredentials").withCause(e), + tracers); + } + if (!x509ExtendedTrustManager.isPresent()) { + return new FailingClientStream(Status.UNAVAILABLE.withDescription( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not " + + "available"), tracers); + } + try { + Certificate[] peerCertificates = sslSession.getPeerCertificates(); + X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; + for (int i = 0; i < peerCertificates.length; i++) { + x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; } + ((X509ExtendedTrustManager) x509ExtendedTrustManager.get()).checkServerTrusted( + x509PeerCertificates, "RSA", + new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); + peerVerificationStatus = Status.OK; + } catch (SSLPeerUnverifiedException | CertificateException e) { + peerVerificationStatus = Status.UNAVAILABLE.withDescription( + String.format("Failure in verifying authority '%s' against peer", + callOptions.getAuthority())).withCause(e); } peerVerificationResults.put(callOptions.getAuthority(), peerVerificationStatus); } @@ -493,7 +504,8 @@ public ClientStream newStream( private Optional getX509ExtendedTrustManager(TlsChannelCredentials tlsCreds) throws GeneralSecurityException { TrustManager[] tm = null; - // Using the same way of creating TrustManager from {@link OkHttpChannelBuilder#sslSocketFactoryFrom}. + // Using the same way of creating TrustManager from + // {@link OkHttpChannelBuilder#sslSocketFactoryFrom}. if (tlsCreds.getTrustManagers() != null) { tm = tlsCreds.getTrustManagers().toArray(new TrustManager[0]); } else if (tlsCreds.getRootCertificates() != null) { @@ -504,7 +516,9 @@ private Optional getX509ExtendedTrustManager(TlsChannelCredentials tmf.init((KeyStore) null); tm = tmf.getTrustManagers(); } - return Arrays.stream(tm).filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + return Arrays.stream(tm).filter( + trustManager -> trustManager instanceof X509ExtendedTrustManager) + .findFirst(); } @GuardedBy("lock") @@ -1690,6 +1704,7 @@ public String getPeerHost() { } @SuppressWarnings("deprecation") + @Override public javax.security.cert.X509Certificate[] getPeerCertificateChain() { throw new UnsupportedOperationException("This method is deprecated and marked for removal. " + "Use the getPeerCertificates() method instead."); diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java index 54f2e108e15..c8343db7c77 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java @@ -72,8 +72,10 @@ import io.grpc.internal.ClientStream; import io.grpc.internal.ClientStreamListener; import io.grpc.internal.ClientTransport; +import io.grpc.internal.FailingClientStream; import io.grpc.internal.FakeClock; import io.grpc.internal.GrpcUtil; +import io.grpc.internal.InsightBuilder; import io.grpc.internal.ManagedClientTransport; import io.grpc.okhttp.OkHttpClientTransport.ClientFrameHandler; import io.grpc.okhttp.OkHttpFrameLogger.Direction; @@ -117,6 +119,10 @@ import java.util.logging.Logger; import javax.annotation.Nullable; import javax.net.SocketFactory; +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; import okio.Buffer; import okio.BufferedSink; import okio.BufferedSource; @@ -191,16 +197,24 @@ public void tearDown() { private void initTransport() throws Exception { startTransport( - DEFAULT_START_STREAM_ID, null, true, null); + DEFAULT_START_STREAM_ID, null, true, null, null); } private void initTransport(int startId) throws Exception { - startTransport(startId, null, true, null); + startTransport(startId, null, true, null, null); } private void startTransport(int startId, @Nullable Runnable connectingCallback, - boolean waitingForConnected, String userAgent) - throws Exception { + boolean waitingForConnected, String userAgent, + HostnameVerifier hostnameVerifier) throws Exception { + startTransport(startId, connectingCallback, waitingForConnected, userAgent, hostnameVerifier, + false); + } + + private void startTransport(int startId, @Nullable Runnable connectingCallback, + boolean waitingForConnected, String userAgent, + HostnameVerifier hostnameVerifier, boolean useSslSocket) + throws Exception { connectedFuture = SettableFuture.create(); final Ticker ticker = new Ticker() { @Override @@ -214,7 +228,11 @@ public Stopwatch get() { return Stopwatch.createUnstarted(ticker); } }; - channelBuilder.socketFactory(new FakeSocketFactory(socket)); + channelBuilder.socketFactory( + new FakeSocketFactory(useSslSocket ? new MockSslSocket(socket) : socket)); + if (hostnameVerifier != null) { + channelBuilder = channelBuilder.hostnameVerifier(hostnameVerifier); + } clientTransport = new OkHttpClientTransport( channelBuilder.buildTransportFactory(), userAgent, @@ -700,7 +718,7 @@ public void addDefaultUserAgent() throws Exception { @Test public void overrideDefaultUserAgent() throws Exception { - startTransport(3, null, true, "fakeUserAgent"); + startTransport(3, null, true, "fakeUserAgent", null); MockStreamListener listener = new MockStreamListener(); ClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); @@ -750,6 +768,70 @@ public void writeMessage() throws Exception { shutdownAndVerify(); } + @Test + public void perRpcAuthoritySpecified_verificationSkippedInPlainTextConnection() + throws Exception { + initTransport(); + final String message = "Hello Server"; + MockStreamListener listener = new MockStreamListener(); + ClientStream stream = + clientTransport.newStream(method, new Metadata(), + CallOptions.DEFAULT.withAuthority("some-authority"), tracers); + stream.start(listener); + InputStream input = new ByteArrayInputStream(message.getBytes(UTF_8)); + assertEquals(12, input.available()); + stream.writeMessage(input); + stream.flush(); + verify(frameWriter, timeout(TIME_OUT_MS)) + .data(eq(false), eq(3), any(Buffer.class), eq(12 + HEADER_LENGTH)); + Buffer sentFrame = capturedBuffer.poll(); + assertEquals(createMessageFrame(message), sentFrame); + stream.cancel(Status.CANCELLED); + shutdownAndVerify(); + } + + @Test + public void perRpcAuthoritySpecified_hostnameVerification_ignoredForNonSslSocket() + throws Exception { + startTransport( + DEFAULT_START_STREAM_ID, null, true, null, + (hostname, session) -> false, false); + ClientStream unused = + clientTransport.newStream(method, new Metadata(), + CallOptions.DEFAULT.withAuthority("some-authority"), tracers); + shutdownAndVerify(); + } + + @Test + public void perRpcAuthoritySpecified_hostnameVerification_SslSocket_successCase() + throws Exception { + startTransport( + DEFAULT_START_STREAM_ID, null, true, null, + (hostname, session) -> true, true); + ClientStream unused = + clientTransport.newStream(method, new Metadata(), + CallOptions.DEFAULT.withAuthority("some-authority"), tracers); + shutdownAndVerify(); + } + + @Test + public void perRpcAuthoritySpecified_hostnameVerification_SslSocket_failureCase() + throws Exception { + startTransport( + DEFAULT_START_STREAM_ID, null, true, null, + (hostname, session) -> false, true); + ClientStream clientStream = + clientTransport.newStream(method, new Metadata(), + CallOptions.DEFAULT.withAuthority("some-authority"), tracers); + assertThat(clientStream).isInstanceOf(FailingClientStream.class); + InsightBuilder insightBuilder = new InsightBuilder(); + clientStream.appendTimeoutInsight(insightBuilder); + assertThat(insightBuilder.toString()).contains("error=Status{code=UNAVAILABLE, " + + "description=HostNameVerifier verification failed for authority 'some-authority', " + + "cause=null}"); + shutdownAndVerify(); + } + @Test public void transportTracer_windowSizeDefault() throws Exception { initTransport(); @@ -1714,7 +1796,7 @@ public void shutdownDuringConnecting() throws Exception { DEFAULT_START_STREAM_ID, connectingCallback, false, - null); + null, null); clientTransport.shutdown(SHUTDOWN_REASON); delayed.set(null); shutdownAndVerify(); @@ -2394,6 +2476,124 @@ public InputStream getInputStream() { } } + private static class MockSslSocket extends SSLSocket { + private Socket delegate; + + MockSslSocket(Socket socket) { + delegate = socket; + } + + @Override + public String[] getSupportedCipherSuites() { + return new String[0]; + } + + @Override + public String[] getEnabledCipherSuites() { + return new String[0]; + } + + @Override + public void setEnabledCipherSuites(String[] suites) { + + } + + @Override + public String[] getSupportedProtocols() { + return new String[0]; + } + + @Override + public String[] getEnabledProtocols() { + return new String[0]; + } + + @Override + public void setEnabledProtocols(String[] protocols) { + + } + + @Override + public SSLSession getSession() { + return null; + } + + @Override + public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { + + } + + @Override + public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { + + } + + @Override + public void startHandshake() throws IOException { + + } + + @Override + public void setUseClientMode(boolean mode) { + + } + + @Override + public boolean getUseClientMode() { + return false; + } + + @Override + public void setNeedClientAuth(boolean need) { + + } + + @Override + public boolean getNeedClientAuth() { + return false; + } + + @Override + public void setWantClientAuth(boolean want) { + + } + + @Override + public boolean getWantClientAuth() { + return false; + } + + @Override + public void setEnableSessionCreation(boolean flag) { + + } + + @Override + public boolean getEnableSessionCreation() { + return false; + } + + @Override + public synchronized void close() throws IOException { + delegate.close(); + } + + @Override + public SocketAddress getLocalSocketAddress() { + return delegate.getLocalSocketAddress(); + } + + @Override + public OutputStream getOutputStream() throws IOException { + return delegate.getOutputStream(); + } + + @Override + public InputStream getInputStream() throws IOException { + return delegate.getInputStream(); + } + } + static class PingCallbackImpl implements ClientTransport.PingCallback { int invocationCount; long roundTripTime; diff --git a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java index a21360a89ba..c7cc9f196bb 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java @@ -18,8 +18,10 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; import com.google.common.base.Throwables; +import io.grpc.CallOptions; import io.grpc.ChannelCredentials; import io.grpc.ConnectivityState; import io.grpc.ManagedChannel; @@ -32,18 +34,31 @@ import io.grpc.TlsServerCredentials; import io.grpc.internal.testing.TestUtils; import io.grpc.okhttp.internal.Platform; +import io.grpc.stub.ClientCalls; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.TlsTesting; import io.grpc.testing.protobuf.SimpleRequest; import io.grpc.testing.protobuf.SimpleResponse; import io.grpc.testing.protobuf.SimpleServiceGrpc; +import io.grpc.util.CertificateUtils; import java.io.IOException; import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Optional; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; import org.junit.Assume; import org.junit.Before; import org.junit.Rule; @@ -92,6 +107,100 @@ public void basicTls_succeeds() throws Exception { SimpleServiceGrpc.newBlockingStub(channel).unaryRpc(SimpleRequest.getDefaultInstance()); } + @Test + public void perRpcAuthorityOverride_matchesCertNames_succeeds() throws Exception { + ServerCredentials serverCreds; + try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); + InputStream serverPrivateKey = TlsTesting.loadCert("server1.key")) { + serverCreds = TlsServerCredentials.newBuilder() + .keyManager(serverCert, serverPrivateKey) + .build(); + } + ChannelCredentials channelCreds; + try (InputStream caCert = TlsTesting.loadCert("ca.pem")) { + channelCreds = TlsChannelCredentials.newBuilder() + .trustManager(caCert) + .build(); + } + Server server = grpcCleanupRule.register(server(serverCreds)); + ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); + + ClientCalls.blockingUnaryCall(channel, SimpleServiceGrpc.getUnaryRpcMethod(), + CallOptions.DEFAULT.withAuthority("foo.test.google.fr"), + SimpleRequest.getDefaultInstance()); + } + + @Test + public void perRpcAuthorityOverride_doesntMatchCertNames_fails() throws Exception { + ServerCredentials serverCreds; + try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); + InputStream serverPrivateKey = TlsTesting.loadCert("server1.key")) { + serverCreds = TlsServerCredentials.newBuilder() + .keyManager(serverCert, serverPrivateKey) + .build(); + } + ChannelCredentials channelCreds; + try (InputStream caCert = TlsTesting.loadCert("ca.pem")) { + channelCreds = TlsChannelCredentials.newBuilder() + .trustManager(caCert) + .build(); + } + Server server = grpcCleanupRule.register(server(serverCreds)); + ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); + + try { + ClientCalls.blockingUnaryCall(channel, SimpleServiceGrpc.getUnaryRpcMethod(), + CallOptions.DEFAULT.withAuthority("foo.test.google.in"), + SimpleRequest.getDefaultInstance()); + fail("Expected exception for authority not matching cert name."); + } catch (StatusRuntimeException ex) { + assertThat(ex.getStatus().getCode()).isEqualTo(Status.Code.UNAVAILABLE); + assertThat(ex.getStatus().getDescription()).isEqualTo( + "Failure in verifying authority 'foo.test.google.in' against peer"); + assertThat(ex.getStatus().getCause()).isInstanceOf(CertificateException.class); + assertThat(ex.getStatus().getCause().getMessage()).isEqualTo( + "No subject alternative DNS name matching foo.test.google.in found."); + } + } + + /** + * This negative test simulates the absence of X509ExtendedTrustManager while still using the + * real trust manager for the connection handshake to happen. + */ + @Test + public void perRpcAuthorityOverride_tlsCreds_noX509ExtendedTrustManager_fails() + throws Exception { + ServerCredentials serverCreds; + try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); + InputStream serverPrivateKey = TlsTesting.loadCert("server1.key")) { + serverCreds = TlsServerCredentials.newBuilder() + .keyManager(serverCert, serverPrivateKey) + .build(); + } + ChannelCredentials channelCreds; + try (InputStream caCert = TlsTesting.loadCert("ca.pem")) { + X509TrustManager x509ExtendedTrustManager = + (X509TrustManager) getX509ExtendedTrustManager(caCert).get(); + channelCreds = TlsChannelCredentials.newBuilder() + .trustManager(new FakeTrustManager(x509ExtendedTrustManager)) + .build(); + } + Server server = grpcCleanupRule.register(server(serverCreds)); + ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); + + try { + ClientCalls.blockingUnaryCall(channel, SimpleServiceGrpc.getUnaryRpcMethod(), + CallOptions.DEFAULT.withAuthority("foo.test.google.in"), + SimpleRequest.getDefaultInstance()); + fail("Expected exception for authority not matching cert name."); + } catch (StatusRuntimeException ex) { + assertThat(ex.getStatus().getCode()).isEqualTo(Status.Code.UNAVAILABLE); + assertThat(ex.getStatus().getDescription()).isEqualTo( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not " + + "available"); + } + } + @Test public void mtls_succeeds() throws Exception { ServerCredentials serverCreds; @@ -282,6 +391,54 @@ public void hostnameVerifierFails_fails() assertThat(status.getCause()).isInstanceOf(SSLPeerUnverifiedException.class); } + private static class FakeTrustManager implements X509TrustManager { + + private final X509TrustManager delegate; + + public FakeTrustManager(X509TrustManager x509ExtendedTrustManager) { + this.delegate = x509ExtendedTrustManager; + } + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) + throws CertificateException { + delegate.checkClientTrusted(x509Certificates, s); + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) + throws CertificateException { + delegate.checkServerTrusted(x509Certificates, s); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return delegate.getAcceptedIssuers(); + } + } + + private static Optional getX509ExtendedTrustManager(InputStream rootCerts) + throws GeneralSecurityException { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + try { + ks.load(null, null); + } catch (IOException ex) { + // Shouldn't really happen, as we're not loading any data. + throw new GeneralSecurityException(ex); + } + X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); + for (X509Certificate cert : certs) { + X500Principal principal = cert.getSubjectX500Principal(); + ks.setCertificateEntry(principal.getName("RFC2253"), cert); + } + + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(ks); + return Arrays.stream(trustManagerFactory.getTrustManagers()) + .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + } + private static Server server(ServerCredentials creds) throws IOException { return OkHttpServerBuilder.forPort(0, creds) .directExecutor() diff --git a/util/src/main/java/io/grpc/util/CertificateUtils.java b/util/src/main/java/io/grpc/util/CertificateUtils.java index a91719b1b41..e7082f177ab 100644 --- a/util/src/main/java/io/grpc/util/CertificateUtils.java +++ b/util/src/main/java/io/grpc/util/CertificateUtils.java @@ -18,7 +18,11 @@ import com.google.common.io.BaseEncoding; import io.grpc.ExperimentalApi; -import java.io.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; From 3d744d97c4e63344e7bbbeb85cdc5d37ec14ae65 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Mon, 16 Dec 2024 19:41:24 +0530 Subject: [PATCH 34/70] Review comments. --- .../io/grpc/netty/ProtocolNegotiators.java | 31 +++++++------ .../grpc/netty/NettyClientTransportTest.java | 8 ++-- .../io/grpc/okhttp/OkHttpChannelBuilder.java | 45 +++++++++++++++---- .../grpc/okhttp/OkHttpChannelBuilderTest.java | 5 ++- 4 files changed, 62 insertions(+), 27 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index de935608839..af7c4fc15af 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -88,6 +88,7 @@ import java.util.logging.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; @@ -145,10 +146,15 @@ public static FromChannelCredentialsResult from(ChannelCredentials creds) { trustManagers = Arrays.asList(tmf.getTrustManagers()); } builder.trustManager(new FixedTrustManagerFactory(trustManagers)); - Optional x509ExtendedTrustManager = trustManagers.stream().filter( - trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + TrustManager x509ExtendedTrustManager = null; + for (TrustManager trustManager: trustManagers) { + if (trustManager instanceof X509ExtendedTrustManager) { + x509ExtendedTrustManager = trustManager; + break; + } + } return FromChannelCredentialsResult.negotiator(tlsClientFactory(builder.build(), - (X509ExtendedTrustManager) x509ExtendedTrustManager.orElse(null))); + (X509ExtendedTrustManager) x509ExtendedTrustManager)); } catch (SSLException | GeneralSecurityException ex) { log.log(Level.FINE, "Exception building SslContext", ex); return FromChannelCredentialsResult.error( @@ -567,6 +573,7 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws } static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { + @GuardedBy("this") private final LinkedHashMap peerVerificationResults = new LinkedHashMap() { @Override @@ -617,10 +624,12 @@ public void close() { @Override public synchronized Status verifyAuthority(@Nonnull String authority) { - if (!canVerifyAuthorityOverride()) { + // sslEngine won't be set when creating ClientTlsHandler from InternalProtocolNegotiators + // for example. + if (sslEngine == null || x509ExtendedTrustManager == null) { return Status.FAILED_PRECONDITION.withDescription( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not " - + "available"); + "Can't allow authority override in rpc when SslEngine or X509ExtendedTrustManager" + + " is not available"); } if (peerVerificationResults.containsKey(authority)) { return peerVerificationResults.get(authority); @@ -631,7 +640,7 @@ public synchronized Status verifyAuthority(@Nonnull String authority) { peerVerificationStatus = Status.OK; } catch (SSLPeerUnverifiedException | CertificateException e) { peerVerificationStatus = Status.UNAVAILABLE.withDescription( - String.format("Peer hostname verification failed for authority '%s'", + String.format("Peer hostname verification during rpc failed for authority '%s'", authority)).withCause(e); } peerVerificationResults.put(authority, peerVerificationStatus); @@ -643,17 +652,11 @@ public void setSslEngine(SSLEngine sslEngine) { this.sslEngine = sslEngine; } - boolean canVerifyAuthorityOverride() { - // sslEngine won't be set when creating ClientTlsHandlder from InternalProtocolNegotiators - // for example. - return sslEngine != null && x509ExtendedTrustManager != null; - } - private void verifyAuthorityAllowedForPeerCert(String authority) throws SSLPeerUnverifiedException, CertificateException { SSLEngine sslEngineWrapper = new SslEngineWrapper(sslEngine, authority); // The typecasting of Certificate to X509Certificate should work because this method will only - // be called when there is a X509ExtendedTrustManager available. + // be called when using TLS and thus X509. Certificate[] peerCertificates = sslEngine.getSession().getPeerCertificates(); X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; for (int i = 0; i < peerCertificates.length; i++) { diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 0cc7fae4a81..94f6c1f764b 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -867,7 +867,7 @@ Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.i stream.appendTimeoutInsight(insightBuilder); assertThat(insightBuilder.toString()).contains( "Status{code=FAILED_PRECONDITION, description=Can't allow authority override in rpc when " - + "X509ExtendedTrustManager is not available, cause=null}"); + + "SslEngine or X509ExtendedTrustManager is not available, cause=null}"); } @Test @@ -891,8 +891,10 @@ Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.i InsightBuilder insightBuilder = new InsightBuilder(); stream.appendTimeoutInsight(insightBuilder); assertThat(insightBuilder.toString()).contains( - "Status{code=UNAVAILABLE, description=Peer hostname verification failed for authority" - + " 'foo.test.google.in', cause=null}"); + "Status{code=UNAVAILABLE, description=Peer hostname verification during rpc failed for" + + " authority 'foo.test.google.in'"); + assertThat(insightBuilder.toString()).contains("cause=java.security.cert.CertificateException:" + + " No subject alternative DNS name matching foo.test.google.in found."); } @Test diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java index 51d7551ccf2..a0fb795a8c3 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java @@ -16,13 +16,37 @@ package io.grpc.okhttp; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS; +import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED; + import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; -import io.grpc.*; -import io.grpc.internal.*; +import io.grpc.CallCredentials; +import io.grpc.ChannelCredentials; +import io.grpc.ChannelLogger; +import io.grpc.ChoiceChannelCredentials; +import io.grpc.CompositeCallCredentials; +import io.grpc.CompositeChannelCredentials; +import io.grpc.ExperimentalApi; +import io.grpc.ForwardingChannelBuilder2; +import io.grpc.InsecureChannelCredentials; +import io.grpc.Internal; +import io.grpc.ManagedChannelBuilder; +import io.grpc.TlsChannelCredentials; +import io.grpc.internal.AtomicBackoff; +import io.grpc.internal.ClientTransportFactory; +import io.grpc.internal.ConnectionClientTransport; +import io.grpc.internal.FixedObjectPool; +import io.grpc.internal.GrpcUtil; +import io.grpc.internal.KeepAliveManager; +import io.grpc.internal.ManagedChannelImplBuilder; import io.grpc.internal.ManagedChannelImplBuilder.ChannelBuilderDefaultPortProvider; import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder; +import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourceHolder.Resource; +import io.grpc.internal.SharedResourcePool; +import io.grpc.internal.TransportTracer; import io.grpc.okhttp.internal.CipherSuite; import io.grpc.okhttp.internal.ConnectionSpec; import io.grpc.okhttp.internal.Platform; @@ -41,17 +65,22 @@ import java.util.Collections; import java.util.EnumSet; import java.util.Set; -import java.util.concurrent.*; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; import javax.net.SocketFactory; -import javax.net.ssl.*; - -import static com.google.common.base.Preconditions.checkNotNull; -import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS; -import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; /** Convenience class for building channels with the OkHttp transport. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1785") diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java index 3670cd057c1..c86e80656e3 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java @@ -34,6 +34,7 @@ import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; import io.grpc.TlsChannelCredentials; +import io.grpc.internal.CertificateUtils; import io.grpc.internal.ClientTransportFactory; import io.grpc.internal.ClientTransportFactory.SwapChannelCredentialsResult; import io.grpc.internal.FakeClock; @@ -212,7 +213,7 @@ public void sslSocketFactoryFrom_tls_mtls() throws Exception { TrustManager[] trustManagers; try (InputStream ca = TlsTesting.loadCert("ca.pem")) { - trustManagers = OkHttpChannelBuilder.createTrustManager(ca); + trustManagers = CertificateUtils.createTrustManager(ca); } SSLContext serverContext = SSLContext.getInstance("TLS"); @@ -257,7 +258,7 @@ public void sslSocketFactoryFrom_tls_mtls_keyFile() throws Exception { InputStream ca = TlsTesting.loadCert("ca.pem")) { serverContext.init( OkHttpChannelBuilder.createKeyManager(server1Chain, server1Key), - OkHttpChannelBuilder.createTrustManager(ca), + CertificateUtils.createTrustManager(ca), null); } final SSLServerSocket serverListenSocket = From 746717e9981c08e258c395b02d42735ca296e4d3 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Mon, 16 Dec 2024 22:02:47 +0530 Subject: [PATCH 35/70] Revert unintended changes. --- examples/example-tls/build.gradle | 4 +++- .../io/grpc/examples/helloworldtls/HelloWorldClientTls.java | 6 ++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 55a07f0d898..dda91e12395 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -76,6 +76,8 @@ application { applicationDistribution.into('bin') { from(helloWorldTlsServer) from(helloWorldTlsClient) - fileMode = 0755 + filePermissions { + unix(0755) + } } } diff --git a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java index 892d873c19c..ca7295cba9f 100644 --- a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java +++ b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java @@ -53,9 +53,7 @@ public void greet(String name) { HelloRequest request = HelloRequest.newBuilder().setName(name).build(); HelloReply response; try { - response = io.grpc.stub.ClientCalls.blockingUnaryCall( - blockingStub.getChannel(), GreeterGrpc.getSayHelloMethod(), - blockingStub.getCallOptions().withAuthority("foo.test.google.in"), request); + response = blockingStub.sayHello(request); } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); return; @@ -90,7 +88,7 @@ public static void main(String[] args) throws Exception { } String host = args[0]; int port = Integer.parseInt(args[1]); - ManagedChannel channel = OkHttpChannelBuilder.forAddress(host, port, tlsBuilder.build()) + ManagedChannel channel = Grpc.newChannelBuilderForAddress(host, port, tlsBuilder.build()) /* Only for using provided test certs. */ .overrideAuthority("foo.test.google.fr") .build(); From 0d310b3fbf606254474fa70074eaeebf9f2be344 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Mon, 16 Dec 2024 22:17:26 +0530 Subject: [PATCH 36/70] Revert unintended changes. --- .../io/grpc/examples/helloworldtls/HelloWorldClientTls.java | 1 - okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java | 2 +- okhttp/src/test/java/io/grpc/okhttp/TlsTest.java | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java index ca7295cba9f..4ff7e23a299 100644 --- a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java +++ b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java @@ -18,7 +18,6 @@ import io.grpc.Channel; import io.grpc.Grpc; -import io.grpc.okhttp.OkHttpChannelBuilder; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; import io.grpc.TlsChannelCredentials; diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 9eddf5468ae..c682b73c69e 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -472,7 +472,7 @@ public ClientStream newStream( peerVerificationStatus = Status.OK; } catch (SSLPeerUnverifiedException | CertificateException e) { peerVerificationStatus = Status.UNAVAILABLE.withDescription( - String.format("Failure in verifying authority '%s' against peer", + String.format("Failure in verifying authority '%s' against peer during rpc", callOptions.getAuthority())).withCause(e); } peerVerificationResults.put(callOptions.getAuthority(), peerVerificationStatus); diff --git a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java index c7cc9f196bb..af2e222cde5 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java @@ -156,7 +156,7 @@ public void perRpcAuthorityOverride_doesntMatchCertNames_fails() throws Exceptio } catch (StatusRuntimeException ex) { assertThat(ex.getStatus().getCode()).isEqualTo(Status.Code.UNAVAILABLE); assertThat(ex.getStatus().getDescription()).isEqualTo( - "Failure in verifying authority 'foo.test.google.in' against peer"); + "Failure in verifying authority 'foo.test.google.in' against peer during rpc"); assertThat(ex.getStatus().getCause()).isInstanceOf(CertificateException.class); assertThat(ex.getStatus().getCause().getMessage()).isEqualTo( "No subject alternative DNS name matching foo.test.google.in found."); From 4ef0fdb680ca7c3a5ccb5de70214a4719d38c11e Mon Sep 17 00:00:00 2001 From: deadEternally Date: Wed, 18 Dec 2024 11:56:34 +0530 Subject: [PATCH 37/70] Move NoopSslSession to io.grpc.internal under grpc-core project. --- .../src/main/java/io/grpc/internal}/NoopSslSession.java | 4 ++-- netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) rename {netty/src/main/java/io/grpc/netty => core/src/main/java/io/grpc/internal}/NoopSslSession.java (97%) diff --git a/netty/src/main/java/io/grpc/netty/NoopSslSession.java b/core/src/main/java/io/grpc/internal/NoopSslSession.java similarity index 97% rename from netty/src/main/java/io/grpc/netty/NoopSslSession.java rename to core/src/main/java/io/grpc/internal/NoopSslSession.java index 647a7deb7ab..9a79d281ad5 100644 --- a/netty/src/main/java/io/grpc/netty/NoopSslSession.java +++ b/core/src/main/java/io/grpc/internal/NoopSslSession.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.netty; +package io.grpc.internal; import java.security.Principal; import java.security.cert.Certificate; @@ -25,7 +25,7 @@ /** A no-op ssl session, to facilitate overriding only the required methods in specific * implementations. */ -class NoopSslSession implements SSLSession { +public class NoopSslSession implements SSLSession { @Override public byte[] getId() { return new byte[0]; diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index af7c4fc15af..bdaed89aada 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -44,6 +44,7 @@ import io.grpc.internal.CertificateUtils; import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; +import io.grpc.internal.NoopSslSession; import io.grpc.internal.ObjectPool; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFutureListener; From 3e2ef723e52c64fa4ed3721c09e3370ca0a4a590 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Wed, 18 Dec 2024 13:04:34 +0530 Subject: [PATCH 38/70] Address review comments. --- .../java/io/grpc/ManagedChannelRegistry.java | 28 +-- .../java/io/grpc/internal/NoopSslSession.java | 132 ++++++++++ examples/example-tls/build.gradle | 2 - .../java/io/grpc/okhttp/NoopSslSocket.java | 117 +++++++++ .../io/grpc/okhttp/OkHttpClientTransport.java | 235 ++---------------- 5 files changed, 283 insertions(+), 231 deletions(-) create mode 100644 core/src/main/java/io/grpc/internal/NoopSslSession.java create mode 100644 okhttp/src/main/java/io/grpc/okhttp/NoopSslSocket.java diff --git a/api/src/main/java/io/grpc/ManagedChannelRegistry.java b/api/src/main/java/io/grpc/ManagedChannelRegistry.java index d6fba4beca6..3d0b1647bda 100644 --- a/api/src/main/java/io/grpc/ManagedChannelRegistry.java +++ b/api/src/main/java/io/grpc/ManagedChannelRegistry.java @@ -99,10 +99,10 @@ public int compare(ManagedChannelProvider o1, ManagedChannelProvider o2) { public static synchronized ManagedChannelRegistry getDefaultRegistry() { if (instance == null) { List providerList = ServiceProviders.loadAll( - ManagedChannelProvider.class, - getHardCodedClasses(), - ManagedChannelProvider.class.getClassLoader(), - new ManagedChannelPriorityAccessor()); + ManagedChannelProvider.class, + getHardCodedClasses(), + ManagedChannelProvider.class.getClassLoader(), + new ManagedChannelPriorityAccessor()); instance = new ManagedChannelRegistry(); for (ManagedChannelProvider provider : providerList) { logger.fine("Service loader found " + provider); @@ -157,7 +157,7 @@ ManagedChannelBuilder newChannelBuilder(String target, ChannelCredentials cre @VisibleForTesting ManagedChannelBuilder newChannelBuilder(NameResolverRegistry nameResolverRegistry, - String target, ChannelCredentials creds) { + String target, ChannelCredentials creds) { NameResolverProvider nameResolverProvider = null; try { URI uri = new URI(target); @@ -167,23 +167,23 @@ ManagedChannelBuilder newChannelBuilder(NameResolverRegistry nameResolverRegi } if (nameResolverProvider == null) { nameResolverProvider = nameResolverRegistry.getProviderForScheme( - nameResolverRegistry.getDefaultScheme()); + nameResolverRegistry.getDefaultScheme()); } Collection> nameResolverSocketAddressTypes - = (nameResolverProvider != null) - ? nameResolverProvider.getProducedSocketAddressTypes() : - Collections.emptySet(); + = (nameResolverProvider != null) + ? nameResolverProvider.getProducedSocketAddressTypes() : + Collections.emptySet(); List providers = providers(); if (providers.isEmpty()) { throw new ProviderNotFoundException("No functional channel service provider found. " - + "Try adding a dependency on the grpc-okhttp, grpc-netty, or grpc-netty-shaded " - + "artifact"); + + "Try adding a dependency on the grpc-okhttp, grpc-netty, or grpc-netty-shaded " + + "artifact"); } StringBuilder error = new StringBuilder(); for (ManagedChannelProvider provider : providers()) { Collection> channelProviderSocketAddressTypes - = provider.getSupportedSocketAddressTypes(); + = provider.getSupportedSocketAddressTypes(); if (!channelProviderSocketAddressTypes.containsAll(nameResolverSocketAddressTypes)) { error.append("; "); error.append(provider.getClass().getName()); @@ -192,7 +192,7 @@ ManagedChannelBuilder newChannelBuilder(NameResolverRegistry nameResolverRegi continue; } ManagedChannelProvider.NewChannelBuilderResult result - = provider.newChannelBuilder(target, creds); + = provider.newChannelBuilder(target, creds); if (result.getChannelBuilder() != null) { return result.getChannelBuilder(); } @@ -205,7 +205,7 @@ ManagedChannelBuilder newChannelBuilder(NameResolverRegistry nameResolverRegi } private static final class ManagedChannelPriorityAccessor - implements ServiceProviders.PriorityAccessor { + implements ServiceProviders.PriorityAccessor { @Override public boolean isAvailable(ManagedChannelProvider provider) { return provider.isAvailable(); diff --git a/core/src/main/java/io/grpc/internal/NoopSslSession.java b/core/src/main/java/io/grpc/internal/NoopSslSession.java new file mode 100644 index 00000000000..9a79d281ad5 --- /dev/null +++ b/core/src/main/java/io/grpc/internal/NoopSslSession.java @@ -0,0 +1,132 @@ +/* + * Copyright 2024 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.internal; + +import java.security.Principal; +import java.security.cert.Certificate; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; + +/** A no-op ssl session, to facilitate overriding only the required methods in specific + * implementations. + */ +public class NoopSslSession implements SSLSession { + @Override + public byte[] getId() { + return new byte[0]; + } + + @Override + public SSLSessionContext getSessionContext() { + return null; + } + + @Override + @SuppressWarnings("deprecation") + public javax.security.cert.X509Certificate[] getPeerCertificateChain() { + throw new UnsupportedOperationException("This method is deprecated and marked for removal. " + + "Use the getPeerCertificates() method instead."); + } + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public long getLastAccessedTime() { + return 0; + } + + @Override + public void invalidate() { + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public void putValue(String s, Object o) { + } + + @Override + public Object getValue(String s) { + return null; + } + + @Override + public void removeValue(String s) { + } + + @Override + public String[] getValueNames() { + return new String[0]; + } + + @Override + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { + return new Certificate[0]; + } + + @Override + public Certificate[] getLocalCertificates() { + return new Certificate[0]; + } + + @Override + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + return null; + } + + @Override + public Principal getLocalPrincipal() { + return null; + } + + @Override + public String getCipherSuite() { + return null; + } + + @Override + public String getProtocol() { + return null; + } + + @Override + public String getPeerHost() { + return null; + } + + @Override + public int getPeerPort() { + return 0; + } + + @Override + public int getPacketBufferSize() { + return 0; + } + + @Override + public int getApplicationBufferSize() { + return 0; + } +} diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index dda91e12395..2d61862700b 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -28,8 +28,6 @@ def grpcVersion = '1.70.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.5' dependencies { - implementation "io.grpc:grpc-api:${grpcVersion}" - implementation "io.grpc:grpc-okhttp:${grpcVersion}" implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" compileOnly "org.apache.tomcat:annotations-api:6.0.53" diff --git a/okhttp/src/main/java/io/grpc/okhttp/NoopSslSocket.java b/okhttp/src/main/java/io/grpc/okhttp/NoopSslSocket.java new file mode 100644 index 00000000000..6e6a6f12a39 --- /dev/null +++ b/okhttp/src/main/java/io/grpc/okhttp/NoopSslSocket.java @@ -0,0 +1,117 @@ +/* + * Copyright 2024 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.okhttp; + +import java.io.IOException; +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +/** A no-op ssl socket, to facilitate overriding only the required methods in specific + * implementations. + */ +class NoopSslSocket extends SSLSocket { + @Override + public String[] getSupportedCipherSuites() { + return new String[0]; + } + + @Override + public String[] getEnabledCipherSuites() { + return new String[0]; + } + + @Override + public void setEnabledCipherSuites(String[] suites) { + + } + + @Override + public String[] getSupportedProtocols() { + return new String[0]; + } + + @Override + public String[] getEnabledProtocols() { + return new String[0]; + } + + @Override + public void setEnabledProtocols(String[] protocols) { + + } + + @Override + public SSLSession getSession() { + return null; + } + + @Override + public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { + + } + + @Override + public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { + + } + + @Override + public void startHandshake() throws IOException { + + } + + @Override + public void setUseClientMode(boolean mode) { + + } + + @Override + public boolean getUseClientMode() { + return false; + } + + @Override + public void setNeedClientAuth(boolean need) { + + } + + @Override + public boolean getNeedClientAuth() { + return false; + } + + @Override + public void setWantClientAuth(boolean want) { + + } + + @Override + public boolean getWantClientAuth() { + return false; + } + + @Override + public void setEnableSessionCreation(boolean flag) { + + } + + @Override + public boolean getEnableSessionCreation() { + return false; + } +} diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index c682b73c69e..69e9b688be8 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -16,10 +16,6 @@ package io.grpc.okhttp; -import static com.google.common.base.Preconditions.checkState; -import static io.grpc.okhttp.Utils.DEFAULT_WINDOW_SIZE; -import static io.grpc.okhttp.Utils.DEFAULT_WINDOW_UPDATE_RATIO; - import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; @@ -44,6 +40,7 @@ import io.grpc.Status.Code; import io.grpc.StatusException; import io.grpc.TlsChannelCredentials; +import io.grpc.internal.CertificateUtils; import io.grpc.internal.ClientStream; import io.grpc.internal.ClientStreamListener.RpcProgress; import io.grpc.internal.ConnectionClientTransport; @@ -54,6 +51,7 @@ import io.grpc.internal.InUseStateAggregator; import io.grpc.internal.KeepAliveManager; import io.grpc.internal.KeepAliveManager.ClientKeepAlivePinger; +import io.grpc.internal.NoopSslSession; import io.grpc.internal.SerializingExecutor; import io.grpc.internal.StatsTraceContext; import io.grpc.internal.TransportTracer; @@ -80,11 +78,9 @@ import java.net.URI; import java.security.GeneralSecurityException; import java.security.KeyStore; -import java.security.Principal; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.util.Arrays; import java.util.Collections; import java.util.Deque; import java.util.EnumMap; @@ -95,7 +91,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Optional; import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; @@ -109,12 +104,10 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import javax.net.SocketFactory; -import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; @@ -128,6 +121,10 @@ import okio.Source; import okio.Timeout; +import static com.google.common.base.Preconditions.checkState; +import static io.grpc.okhttp.Utils.DEFAULT_WINDOW_SIZE; +import static io.grpc.okhttp.Utils.DEFAULT_WINDOW_UPDATE_RATIO; + /** * A okhttp-based {@link ConnectionClientTransport} implementation. */ @@ -446,7 +443,7 @@ public ClientStream newStream( if (peerVerificationResults.containsKey(callOptions.getAuthority())) { peerVerificationStatus = peerVerificationResults.get(callOptions.getAuthority()); } else { - Optional x509ExtendedTrustManager; + TrustManager x509ExtendedTrustManager; try { x509ExtendedTrustManager = getX509ExtendedTrustManager( (TlsChannelCredentials) channelCredentials); @@ -455,7 +452,7 @@ public ClientStream newStream( "Failure getting X509ExtendedTrustManager from TlsCredentials").withCause(e), tracers); } - if (!x509ExtendedTrustManager.isPresent()) { + if (x509ExtendedTrustManager == null) { return new FailingClientStream(Status.UNAVAILABLE.withDescription( "Can't allow authority override in rpc when X509ExtendedTrustManager is not " + "available"), tracers); @@ -466,7 +463,7 @@ public ClientStream newStream( for (int i = 0; i < peerCertificates.length; i++) { x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; } - ((X509ExtendedTrustManager) x509ExtendedTrustManager.get()).checkServerTrusted( + ((X509ExtendedTrustManager) x509ExtendedTrustManager).checkServerTrusted( x509PeerCertificates, "RSA", new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); peerVerificationStatus = Status.OK; @@ -501,24 +498,26 @@ public ClientStream newStream( } } - private Optional getX509ExtendedTrustManager(TlsChannelCredentials tlsCreds) + private TrustManager getX509ExtendedTrustManager(TlsChannelCredentials tlsCreds) throws GeneralSecurityException { TrustManager[] tm = null; - // Using the same way of creating TrustManager from - // {@link OkHttpChannelBuilder#sslSocketFactoryFrom}. + // Using the same way of creating TrustManager from OkHttpChannelBuilder.sslSocketFactoryFrom() if (tlsCreds.getTrustManagers() != null) { tm = tlsCreds.getTrustManagers().toArray(new TrustManager[0]); } else if (tlsCreds.getRootCertificates() != null) { - tm = io.grpc.internal.CertificateUtils.createTrustManager(tlsCreds.getRootCertificates()); + tm = CertificateUtils.createTrustManager(tlsCreds.getRootCertificates()); } else { // else use system default TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); tmf.init((KeyStore) null); tm = tmf.getTrustManagers(); } - return Arrays.stream(tm).filter( - trustManager -> trustManager instanceof X509ExtendedTrustManager) - .findFirst(); + for (TrustManager trustManager: tm) { + if (trustManager instanceof X509ExtendedTrustManager) { + return trustManager; + } + } + return null; } @GuardedBy("lock") @@ -1567,7 +1566,7 @@ public void alternateService(int streamId, String origin, ByteString protocol, S /** * SSLSocket wrapper that provides a fake SSLSession for handshake session. */ - static class SslSocketWrapper extends SSLSocket { + static class SslSocketWrapper extends NoopSslSocket { private final SSLSession sslSession; private final SSLSocket sslSocket; @@ -1593,104 +1592,12 @@ public SSLParameters getSSLParameters() { sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); return sslParameters; } - - @Override - public String[] getSupportedCipherSuites() { - return new String[0]; - } - - @Override - public String[] getEnabledCipherSuites() { - return new String[0]; - } - - @Override - public void setEnabledCipherSuites(String[] strings) { - - } - - @Override - public String[] getSupportedProtocols() { - return new String[0]; - } - - @Override - public String[] getEnabledProtocols() { - return new String[0]; - } - - @Override - public void setEnabledProtocols(String[] strings) { - - } - - @Override - public SSLSession getSession() { - return null; - } - - @Override - public void addHandshakeCompletedListener( - HandshakeCompletedListener handshakeCompletedListener) { - - } - - @Override - public void removeHandshakeCompletedListener( - HandshakeCompletedListener handshakeCompletedListener) { - - } - - @Override - public void startHandshake() throws IOException { - - } - - @Override - public void setUseClientMode(boolean b) { - - } - - @Override - public boolean getUseClientMode() { - return false; - } - - @Override - public void setNeedClientAuth(boolean b) { - - } - - @Override - public boolean getNeedClientAuth() { - return false; - } - - @Override - public void setWantClientAuth(boolean b) { - - } - - @Override - public boolean getWantClientAuth() { - return false; - } - - @Override - public void setEnableSessionCreation(boolean b) { - - } - - @Override - public boolean getEnableSessionCreation() { - return false; - } } /** * Fake SSLSession instance that provides the peer host name to verify for per-rpc check. */ - static class FakeSslSession implements SSLSession { + static class FakeSslSession extends NoopSslSession { private final String peerHost; @@ -1702,107 +1609,5 @@ static class FakeSslSession implements SSLSession { public String getPeerHost() { return peerHost; } - - @SuppressWarnings("deprecation") - @Override - public javax.security.cert.X509Certificate[] getPeerCertificateChain() { - throw new UnsupportedOperationException("This method is deprecated and marked for removal. " - + "Use the getPeerCertificates() method instead."); - } - - @Override - public byte[] getId() { - return new byte[0]; - } - - @Override - public SSLSessionContext getSessionContext() { - return null; - } - - @Override - public long getCreationTime() { - return 0; - } - - @Override - public long getLastAccessedTime() { - return 0; - } - - @Override - public void invalidate() { - - } - - @Override - public boolean isValid() { - return false; - } - - @Override - public void putValue(String s, Object o) { - - } - - @Override - public Object getValue(String s) { - return null; - } - - @Override - public void removeValue(String s) { - - } - - @Override - public String[] getValueNames() { - return new String[0]; - } - - @Override - public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { - return new Certificate[0]; - } - - @Override - public Certificate[] getLocalCertificates() { - return new Certificate[0]; - } - - @Override - public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { - return null; - } - - @Override - public Principal getLocalPrincipal() { - return null; - } - - @Override - public String getCipherSuite() { - return null; - } - - @Override - public String getProtocol() { - return null; - } - - @Override - public int getPeerPort() { - return 0; - } - - @Override - public int getPacketBufferSize() { - return 0; - } - - @Override - public int getApplicationBufferSize() { - return 0; - } } } \ No newline at end of file From 30b1e1469275bd1022b56a359b85f55a2dfe5495 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Wed, 18 Dec 2024 13:12:16 +0530 Subject: [PATCH 39/70] Address review comments. --- .../main/java/io/grpc/okhttp/OkHttpClientTransport.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 69e9b688be8..7fc3dfca642 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -16,6 +16,10 @@ package io.grpc.okhttp; +import static com.google.common.base.Preconditions.checkState; +import static io.grpc.okhttp.Utils.DEFAULT_WINDOW_SIZE; +import static io.grpc.okhttp.Utils.DEFAULT_WINDOW_UPDATE_RATIO; + import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; @@ -121,10 +125,6 @@ import okio.Source; import okio.Timeout; -import static com.google.common.base.Preconditions.checkState; -import static io.grpc.okhttp.Utils.DEFAULT_WINDOW_SIZE; -import static io.grpc.okhttp.Utils.DEFAULT_WINDOW_UPDATE_RATIO; - /** * A okhttp-based {@link ConnectionClientTransport} implementation. */ From fdc6e94fec5000a78bc1b2abf879852fc2a78d36 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Thu, 19 Dec 2024 16:42:26 +0530 Subject: [PATCH 40/70] Added flag with default false for the per-rpc authority check. --- .../io/grpc/netty/NettyClientTransport.java | 6 +- .../grpc/netty/NettyClientTransportTest.java | 135 +++++++++++------- 2 files changed, 90 insertions(+), 51 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index 6360ee61285..b72bb35c74a 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -197,7 +197,11 @@ public ClientStream newStream( if (callOptions.getAuthority() != null) { Status verificationStatus = negotiator.verifyAuthority(callOptions.getAuthority()); if (!verificationStatus.isOk()) { - return new FailingClientStream(verificationStatus, tracers); + if (GrpcUtil.getFlag("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", false)) { + return new FailingClientStream(verificationStatus, tracers); + } + channelLogger.log(ChannelLogger.ChannelLogLevel.WARNING, "Authority '{}' specified via call" + + " options for rpc did not match peer certificate's subject names."); } } StatsTraceContext statsTraceCtx = diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 94f6c1f764b..743d1977284 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -842,64 +842,99 @@ public void tlsNegotiationServerExecutorShouldSucceed() throws Exception { @Test public void authorityOverrideInCallOptions_noX509ExtendedTrustManager_newStreamCreationFails() throws IOException, InterruptedException, GeneralSecurityException { - startServer(); - InputStream caCert = TlsTesting.loadCert("ca.pem"); - X509TrustManager x509ExtendedTrustManager = - (X509TrustManager) getX509ExtendedTrustManager(caCert).get(); - ProtocolNegotiators.FromChannelCredentialsResult result = - ProtocolNegotiators.from(TlsChannelCredentials.newBuilder() - .trustManager(new FakeTrustManager(x509ExtendedTrustManager)).build()); - NettyClientTransport transport = newTransport(result.negotiator.newNegotiator()); - FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); - callMeMaybe(transport.start(fakeClientTransportListener)); - synchronized (fakeClientTransportListener) { - fakeClientTransportListener.wait(10000); + System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); + try { + startServer(); + InputStream caCert = TlsTesting.loadCert("ca.pem"); + X509TrustManager x509ExtendedTrustManager = + (X509TrustManager) getX509ExtendedTrustManager(caCert).get(); + ProtocolNegotiators.FromChannelCredentialsResult result = + ProtocolNegotiators.from(TlsChannelCredentials.newBuilder() + .trustManager(new FakeTrustManager(x509ExtendedTrustManager)).build()); + NettyClientTransport transport = newTransport(result.negotiator.newNegotiator()); + FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); + callMeMaybe(transport.start(fakeClientTransportListener)); + synchronized (fakeClientTransportListener) { + fakeClientTransportListener.wait(10000); + } + assertThat(fakeClientTransportListener.isConnected).isTrue(); + + ClientStream stream = transport.newStream( + Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), + new ClientStreamTracer[]{new ClientStreamTracer() { + }}); + + assertThat(stream).isInstanceOf(FailingClientStream.class); + InsightBuilder insightBuilder = new InsightBuilder(); + stream.appendTimeoutInsight(insightBuilder); + assertThat(insightBuilder.toString()).contains( + "Status{code=FAILED_PRECONDITION, description=Can't allow authority override in rpc when " + + "SslEngine or X509ExtendedTrustManager is not available, cause=null}"); + } finally { + System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); } - assertThat(fakeClientTransportListener.isConnected).isTrue(); - - ClientStream stream = transport.newStream( - Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), - new ClientStreamTracer[]{new ClientStreamTracer() { - }}); - - assertThat(stream).isInstanceOf(FailingClientStream.class); - InsightBuilder insightBuilder = new InsightBuilder(); - stream.appendTimeoutInsight(insightBuilder); - assertThat(insightBuilder.toString()).contains( - "Status{code=FAILED_PRECONDITION, description=Can't allow authority override in rpc when " - + "SslEngine or X509ExtendedTrustManager is not available, cause=null}"); } @Test public void authorityOverrideInCallOptions_doesntMatchServerPeerHost_newStreamCreationFails() throws IOException, InterruptedException, GeneralSecurityException { - startServer(); - NettyClientTransport transport = newTransport(newNegotiator()); - FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); - callMeMaybe(transport.start(fakeClientTransportListener)); - synchronized (fakeClientTransportListener) { - fakeClientTransportListener.wait(10000); + System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); + try { + startServer(); + NettyClientTransport transport = newTransport(newNegotiator()); + FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); + callMeMaybe(transport.start(fakeClientTransportListener)); + synchronized (fakeClientTransportListener) { + fakeClientTransportListener.wait(10000); + } + assertThat(fakeClientTransportListener.isConnected).isTrue(); + + ClientStream stream = transport.newStream( + Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), + new ClientStreamTracer[]{new ClientStreamTracer() { + }}); + + assertThat(stream).isInstanceOf(FailingClientStream.class); + InsightBuilder insightBuilder = new InsightBuilder(); + stream.appendTimeoutInsight(insightBuilder); + assertThat(insightBuilder.toString()).contains( + "Status{code=UNAVAILABLE, description=Peer hostname verification during rpc failed for" + + " authority 'foo.test.google.in'"); + assertThat(insightBuilder.toString()).contains("cause=java.security.cert.CertificateException:" + + " No subject alternative DNS name matching foo.test.google.in found."); + } finally { + System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); } - assertThat(fakeClientTransportListener.isConnected).isTrue(); - - ClientStream stream = transport.newStream( - Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), - new ClientStreamTracer[]{new ClientStreamTracer() { - }}); - - assertThat(stream).isInstanceOf(FailingClientStream.class); - InsightBuilder insightBuilder = new InsightBuilder(); - stream.appendTimeoutInsight(insightBuilder); - assertThat(insightBuilder.toString()).contains( - "Status{code=UNAVAILABLE, description=Peer hostname verification during rpc failed for" - + " authority 'foo.test.google.in'"); - assertThat(insightBuilder.toString()).contains("cause=java.security.cert.CertificateException:" - + " No subject alternative DNS name matching foo.test.google.in found."); } @Test public void authorityOverrideInCallOptions_matchesServerPeerHost_newStreamCreationSucceeds() throws IOException, InterruptedException, GeneralSecurityException { + System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); + try { + startServer(); + NettyClientTransport transport = newTransport(newNegotiator()); + FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); + callMeMaybe(transport.start(fakeClientTransportListener)); + synchronized (fakeClientTransportListener) { + fakeClientTransportListener.wait(10000); + } + assertThat(fakeClientTransportListener.isConnected).isTrue(); + + ClientStream stream = transport.newStream( + Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("zoo.test.google.fr"), + new ClientStreamTracer[]{new ClientStreamTracer() { + }}); + + assertThat(stream).isNotInstanceOf(FailingClientStream.class); + } finally { + System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); + } + } + + @Test + public void authorityOverrideInCallOptions_notMatches_flagDisabled_createsStream() + throws IOException, InterruptedException, GeneralSecurityException { startServer(); NettyClientTransport transport = newTransport(newNegotiator()); FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); @@ -910,11 +945,11 @@ public void authorityOverrideInCallOptions_matchesServerPeerHost_newStreamCreati assertThat(fakeClientTransportListener.isConnected).isTrue(); ClientStream stream = transport.newStream( - Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("zoo.test.google.fr"), - new ClientStreamTracer[]{new ClientStreamTracer() { - }}); + Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), + new ClientStreamTracer[]{new ClientStreamTracer() { + }}); - assertThat(stream).isNotInstanceOf(FailingClientStream.class); + assertThat(stream).isInstanceOf(NettyClientStream.class); } private Throwable getRootCause(Throwable t) { From f996474dbce2b2870d7d073212ee2cd421a6d858 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Thu, 19 Dec 2024 22:57:21 +0530 Subject: [PATCH 41/70] Added flag with default false for the per-rpc authority check and removed setting sslEndpointAlgorithm. --- .../io/grpc/okhttp/OkHttpClientTransport.java | 114 +++++++----- .../okhttp/OkHttpClientTransportTest.java | 30 ++- .../src/test/java/io/grpc/okhttp/TlsTest.java | 174 ++++++++++++------ 3 files changed, 204 insertions(+), 114 deletions(-) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 7fc3dfca642..04a241ad42c 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -132,9 +132,12 @@ class OkHttpClientTransport implements ConnectionClientTransport, TransportExcep OutboundFlowController.Transport { private static final Map ERROR_CODE_TO_STATUS = buildErrorCodeToStatusMap(); private static final Logger log = Logger.getLogger(OkHttpClientTransport.class.getName()); + private static final String GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK = + "GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"; private final ChannelCredentials channelCredentials; private Socket sock; private SSLSession sslSession; + private final Logger logger = Logger.getLogger(OkHttpClientTransport.class.getName()); private static Map buildErrorCodeToStatusMap() { Map errorToStatus = new EnumMap<>(ErrorCode.class); @@ -262,14 +265,14 @@ protected void handleNotInUse() { SettableFuture connectedFuture; public OkHttpClientTransport( - OkHttpTransportFactory transportFactory, - InetSocketAddress address, - String authority, - @Nullable String userAgent, - Attributes eagAttrs, - @Nullable HttpConnectProxiedSocketAddress proxiedAddr, - Runnable tooManyPingsRunnable, - ChannelCredentials channelCredentials) { + OkHttpTransportFactory transportFactory, + InetSocketAddress address, + String authority, + @Nullable String userAgent, + Attributes eagAttrs, + @Nullable HttpConnectProxiedSocketAddress proxiedAddr, + Runnable tooManyPingsRunnable, + ChannelCredentials channelCredentials) { this( transportFactory, address, @@ -284,16 +287,16 @@ public OkHttpClientTransport( } private OkHttpClientTransport( - OkHttpTransportFactory transportFactory, - InetSocketAddress address, - String authority, - @Nullable String userAgent, - Attributes eagAttrs, - Supplier stopwatchFactory, - Variant variant, - @Nullable HttpConnectProxiedSocketAddress proxiedAddr, - Runnable tooManyPingsRunnable, - ChannelCredentials channelCredentials) { + OkHttpTransportFactory transportFactory, + InetSocketAddress address, + String authority, + @Nullable String userAgent, + Attributes eagAttrs, + Supplier stopwatchFactory, + Variant variant, + @Nullable HttpConnectProxiedSocketAddress proxiedAddr, + Runnable tooManyPingsRunnable, + ChannelCredentials channelCredentials) { this.address = Preconditions.checkNotNull(address, "address"); this.defaultAuthority = authority; this.maxMessageSize = transportFactory.maxMessageSize; @@ -433,48 +436,65 @@ public ClientStream newStream( if (hostnameVerifier != null && socket instanceof SSLSocket && !hostnameVerifier.verify(callOptions.getAuthority(), ((SSLSocket) socket).getSession())) { - return new FailingClientStream(Status.UNAVAILABLE.withDescription( - String.format("HostNameVerifier verification failed for authority '%s'", - callOptions.getAuthority())), tracers); + if (GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false)) { + return new FailingClientStream(Status.UNAVAILABLE.withDescription( + String.format("HostNameVerifier verification failed for authority '%s'", + callOptions.getAuthority())), tracers); + } + logger.warning(String.format("HostNameVerifier verification failed for authority '%s'.", + callOptions.getAuthority())); } if (socket instanceof SSLSocket && callOptions.getAuthority() != null && channelCredentials != null && channelCredentials instanceof TlsChannelCredentials) { - Status peerVerificationStatus; + Status peerVerificationStatus = null; if (peerVerificationResults.containsKey(callOptions.getAuthority())) { peerVerificationStatus = peerVerificationResults.get(callOptions.getAuthority()); } else { - TrustManager x509ExtendedTrustManager; + TrustManager x509ExtendedTrustManager = null; + boolean warningLogged = false; try { x509ExtendedTrustManager = getX509ExtendedTrustManager( (TlsChannelCredentials) channelCredentials); } catch (GeneralSecurityException e) { - return new FailingClientStream(Status.UNAVAILABLE.withDescription( - "Failure getting X509ExtendedTrustManager from TlsCredentials").withCause(e), - tracers); + if (GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false)) { + return new FailingClientStream(Status.UNAVAILABLE.withDescription( + "Failure getting X509ExtendedTrustManager from TlsCredentials").withCause(e), + tracers); + } + logger.warning(String.format("Failure getting X509ExtendedTrustManager from " + + "TlsCredentials due to: %s", e.getMessage())); + warningLogged = true; } if (x509ExtendedTrustManager == null) { - return new FailingClientStream(Status.UNAVAILABLE.withDescription( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not " - + "available"), tracers); - } - try { - Certificate[] peerCertificates = sslSession.getPeerCertificates(); - X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; - for (int i = 0; i < peerCertificates.length; i++) { - x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; + if (GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false)) { + return new FailingClientStream(Status.UNAVAILABLE.withDescription( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not " + + "available"), tracers); + } + if (!warningLogged) { + logger.warning("Authority override set for rpc when X509ExtendedTrustManager is not " + + "available."); + } + } else { + try { + Certificate[] peerCertificates = sslSession.getPeerCertificates(); + X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; + for (int i = 0; i < peerCertificates.length; i++) { + x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; + } + ((X509ExtendedTrustManager) x509ExtendedTrustManager).checkServerTrusted( + x509PeerCertificates, "RSA", + new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); + peerVerificationStatus = Status.OK; + } catch (SSLPeerUnverifiedException | CertificateException e) { + peerVerificationStatus = Status.UNAVAILABLE.withDescription( + String.format("Failure in verifying authority '%s' against peer during rpc", + callOptions.getAuthority())).withCause(e); } - ((X509ExtendedTrustManager) x509ExtendedTrustManager).checkServerTrusted( - x509PeerCertificates, "RSA", - new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); - peerVerificationStatus = Status.OK; - } catch (SSLPeerUnverifiedException | CertificateException e) { - peerVerificationStatus = Status.UNAVAILABLE.withDescription( - String.format("Failure in verifying authority '%s' against peer during rpc", - callOptions.getAuthority())).withCause(e); + peerVerificationResults.put(callOptions.getAuthority(), peerVerificationStatus); } - peerVerificationResults.put(callOptions.getAuthority(), peerVerificationStatus); } - if (!peerVerificationStatus.isOk()) { + if (peerVerificationStatus != null && !peerVerificationStatus.isOk()) { return new FailingClientStream(peerVerificationStatus, tracers); } } @@ -1588,9 +1608,7 @@ public boolean isConnected() { @Override public SSLParameters getSSLParameters() { - SSLParameters sslParameters = sslSocket.getSSLParameters(); - sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); - return sslParameters; + return sslSocket.getSSLParameters(); } } diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java index c8343db7c77..5c46e6ebffc 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java @@ -817,18 +817,36 @@ public void perRpcAuthoritySpecified_hostnameVerification_SslSocket_successCase( @Test public void perRpcAuthoritySpecified_hostnameVerification_SslSocket_failureCase() throws Exception { + System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); + try { + startTransport( + DEFAULT_START_STREAM_ID, null, true, null, + (hostname, session) -> false, true); + ClientStream clientStream = + clientTransport.newStream(method, new Metadata(), + CallOptions.DEFAULT.withAuthority("some-authority"), tracers); + assertThat(clientStream).isInstanceOf(FailingClientStream.class); + InsightBuilder insightBuilder = new InsightBuilder(); + clientStream.appendTimeoutInsight(insightBuilder); + assertThat(insightBuilder.toString()).contains("error=Status{code=UNAVAILABLE, " + + "description=HostNameVerifier verification failed for authority 'some-authority', " + + "cause=null}"); + shutdownAndVerify(); + } finally { + System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); + } + } + + @Test + public void perRpcAuthoritySpecified_hostnameVerification_SslSocket_flagDisabled() + throws Exception { startTransport( DEFAULT_START_STREAM_ID, null, true, null, (hostname, session) -> false, true); ClientStream clientStream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT.withAuthority("some-authority"), tracers); - assertThat(clientStream).isInstanceOf(FailingClientStream.class); - InsightBuilder insightBuilder = new InsightBuilder(); - clientStream.appendTimeoutInsight(insightBuilder); - assertThat(insightBuilder.toString()).contains("error=Status{code=UNAVAILABLE, " - + "description=HostNameVerifier verification failed for authority 'some-authority', " - + "cause=null}"); + assertThat(clientStream).isInstanceOf(OkHttpClientStream.class); shutdownAndVerify(); } diff --git a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java index af2e222cde5..67901a9db3c 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java @@ -44,6 +44,7 @@ import io.grpc.util.CertificateUtils; import java.io.IOException; import java.io.InputStream; +import java.net.Socket; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.cert.CertificateException; @@ -108,58 +109,35 @@ public void basicTls_succeeds() throws Exception { } @Test - public void perRpcAuthorityOverride_matchesCertNames_succeeds() throws Exception { - ServerCredentials serverCreds; - try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); - InputStream serverPrivateKey = TlsTesting.loadCert("server1.key")) { - serverCreds = TlsServerCredentials.newBuilder() - .keyManager(serverCert, serverPrivateKey) - .build(); - } - ChannelCredentials channelCreds; - try (InputStream caCert = TlsTesting.loadCert("ca.pem")) { - channelCreds = TlsChannelCredentials.newBuilder() - .trustManager(caCert) - .build(); - } - Server server = grpcCleanupRule.register(server(serverCreds)); - ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); - - ClientCalls.blockingUnaryCall(channel, SimpleServiceGrpc.getUnaryRpcMethod(), - CallOptions.DEFAULT.withAuthority("foo.test.google.fr"), - SimpleRequest.getDefaultInstance()); - } - - @Test - public void perRpcAuthorityOverride_doesntMatchCertNames_fails() throws Exception { - ServerCredentials serverCreds; - try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); - InputStream serverPrivateKey = TlsTesting.loadCert("server1.key")) { - serverCreds = TlsServerCredentials.newBuilder() - .keyManager(serverCert, serverPrivateKey) - .build(); - } - ChannelCredentials channelCreds; - try (InputStream caCert = TlsTesting.loadCert("ca.pem")) { - channelCreds = TlsChannelCredentials.newBuilder() - .trustManager(caCert) - .build(); - } - Server server = grpcCleanupRule.register(server(serverCreds)); - ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); - + public void perRpcAuthorityOverride_checkServerTrustedIsCalled() throws Exception { + System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); try { + ServerCredentials serverCreds; + try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); + InputStream serverPrivateKey = TlsTesting.loadCert("server1.key")) { + serverCreds = TlsServerCredentials.newBuilder() + .keyManager(serverCert, serverPrivateKey) + .build(); + } + ChannelCredentials channelCreds; + FakeX509ExtendedTrustManager fakeTrustManager; + try (InputStream caCert = TlsTesting.loadCert("ca.pem")) { + X509ExtendedTrustManager x509ExtendedTrustManager = + (X509ExtendedTrustManager) getX509ExtendedTrustManager(caCert).get(); + fakeTrustManager = new FakeX509ExtendedTrustManager(x509ExtendedTrustManager); + channelCreds = TlsChannelCredentials.newBuilder() + .trustManager(fakeTrustManager) + .build(); + } + Server server = grpcCleanupRule.register(server(serverCreds)); + ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); + ClientCalls.blockingUnaryCall(channel, SimpleServiceGrpc.getUnaryRpcMethod(), - CallOptions.DEFAULT.withAuthority("foo.test.google.in"), + CallOptions.DEFAULT.withAuthority("foo.test.google.fr"), SimpleRequest.getDefaultInstance()); - fail("Expected exception for authority not matching cert name."); - } catch (StatusRuntimeException ex) { - assertThat(ex.getStatus().getCode()).isEqualTo(Status.Code.UNAVAILABLE); - assertThat(ex.getStatus().getDescription()).isEqualTo( - "Failure in verifying authority 'foo.test.google.in' against peer during rpc"); - assertThat(ex.getStatus().getCause()).isInstanceOf(CertificateException.class); - assertThat(ex.getStatus().getCause().getMessage()).isEqualTo( - "No subject alternative DNS name matching foo.test.google.in found."); + assertThat(fakeTrustManager.checkServerTrustedCalled).isTrue(); + } finally { + System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); } } @@ -170,6 +148,45 @@ public void perRpcAuthorityOverride_doesntMatchCertNames_fails() throws Exceptio @Test public void perRpcAuthorityOverride_tlsCreds_noX509ExtendedTrustManager_fails() throws Exception { + System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); + try { + ServerCredentials serverCreds; + try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); + InputStream serverPrivateKey = TlsTesting.loadCert("server1.key")) { + serverCreds = TlsServerCredentials.newBuilder() + .keyManager(serverCert, serverPrivateKey) + .build(); + } + ChannelCredentials channelCreds; + try (InputStream caCert = TlsTesting.loadCert("ca.pem")) { + X509TrustManager x509ExtendedTrustManager = + (X509TrustManager) getX509ExtendedTrustManager(caCert).get(); + channelCreds = TlsChannelCredentials.newBuilder() + .trustManager(new FakeTrustManager(x509ExtendedTrustManager)) + .build(); + } + Server server = grpcCleanupRule.register(server(serverCreds)); + ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); + + try { + ClientCalls.blockingUnaryCall(channel, SimpleServiceGrpc.getUnaryRpcMethod(), + CallOptions.DEFAULT.withAuthority("foo.test.google.in"), + SimpleRequest.getDefaultInstance()); + fail("Expected exception for authority not matching cert name."); + } catch (StatusRuntimeException ex) { + assertThat(ex.getStatus().getCode()).isEqualTo(Status.Code.UNAVAILABLE); + assertThat(ex.getStatus().getDescription()).isEqualTo( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not " + + "available"); + } + } finally { + System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); + } + } + + @Test + public void perRpcAuthorityOverride_tlsCreds_noX509ExtendedTrustManager_flagDisabled() + throws Exception { ServerCredentials serverCreds; try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); InputStream serverPrivateKey = TlsTesting.loadCert("server1.key")) { @@ -188,17 +205,9 @@ public void perRpcAuthorityOverride_tlsCreds_noX509ExtendedTrustManager_fails() Server server = grpcCleanupRule.register(server(serverCreds)); ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); - try { - ClientCalls.blockingUnaryCall(channel, SimpleServiceGrpc.getUnaryRpcMethod(), - CallOptions.DEFAULT.withAuthority("foo.test.google.in"), - SimpleRequest.getDefaultInstance()); - fail("Expected exception for authority not matching cert name."); - } catch (StatusRuntimeException ex) { - assertThat(ex.getStatus().getCode()).isEqualTo(Status.Code.UNAVAILABLE); - assertThat(ex.getStatus().getDescription()).isEqualTo( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not " - + "available"); - } + ClientCalls.blockingUnaryCall(channel, SimpleServiceGrpc.getUnaryRpcMethod(), + CallOptions.DEFAULT.withAuthority("foo.test.google.in"), + SimpleRequest.getDefaultInstance()); } @Test @@ -391,6 +400,7 @@ public void hostnameVerifierFails_fails() assertThat(status.getCause()).isInstanceOf(SSLPeerUnverifiedException.class); } + /** Used to simulate the case of X509ExtendedTrustManager not present. */ private static class FakeTrustManager implements X509TrustManager { private final X509TrustManager delegate; @@ -417,6 +427,50 @@ public X509Certificate[] getAcceptedIssuers() { } } + /** Used to capture the fact that checkServerTrusted has been called for the per-rpc authority verification. */ + private static class FakeX509ExtendedTrustManager extends X509ExtendedTrustManager { + private final X509ExtendedTrustManager delegate; + private boolean checkServerTrustedCalled; + + private FakeX509ExtendedTrustManager(X509ExtendedTrustManager delegate) { + this.delegate = delegate; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + delegate.checkServerTrusted(chain, authType, socket); + this.checkServerTrustedCalled = true; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { + delegate.checkServerTrusted(chain, authType, engine); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + delegate.checkServerTrusted(chain, authType); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + private static Optional getX509ExtendedTrustManager(InputStream rootCerts) throws GeneralSecurityException { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); From f5b3614c6e3e7e3d7494988bd5be00619a4bbf53 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Mon, 9 Dec 2024 16:22:15 +0000 Subject: [PATCH 42/70] Update README etc to reference 1.69.0 --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 97b2bd6d5f9..c6a8f3bdd8a 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ For a guided tour, take a look at the [quick start guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC basics](https://grpc.io/docs/languages/java/basics). -The [examples](https://github.com/grpc/grpc-java/tree/v1.68.1/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.68.1/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.69.0/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.69.0/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.68.1 + 1.69.0 runtime io.grpc grpc-protobuf - 1.68.1 + 1.69.0 io.grpc grpc-stub - 1.68.1 + 1.69.0 org.apache.tomcat @@ -79,18 +79,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: Or for Gradle with non-Android, add to your dependencies: ```gradle -runtimeOnly 'io.grpc:grpc-netty-shaded:1.68.1' -implementation 'io.grpc:grpc-protobuf:1.68.1' -implementation 'io.grpc:grpc-stub:1.68.1' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.69.0' +implementation 'io.grpc:grpc-protobuf:1.69.0' +implementation 'io.grpc:grpc-stub:1.69.0' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and `grpc-protobuf-lite` instead of `grpc-protobuf`: ```gradle -implementation 'io.grpc:grpc-okhttp:1.68.1' -implementation 'io.grpc:grpc-protobuf-lite:1.68.1' -implementation 'io.grpc:grpc-stub:1.68.1' +implementation 'io.grpc:grpc-okhttp:1.69.0' +implementation 'io.grpc:grpc-protobuf-lite:1.69.0' +implementation 'io.grpc:grpc-stub:1.69.0' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` @@ -99,7 +99,7 @@ For [Bazel](https://bazel.build), you can either (with the GAVs from above), or use `@io_grpc_grpc_java//api` et al (see below). [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.68.1 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.69.0 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -131,7 +131,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.68.1:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.69.0:exe:${os.detected.classifier} @@ -161,7 +161,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.68.1' + artifact = 'io.grpc:protoc-gen-grpc-java:1.69.0' } } generateProtoTasks { @@ -194,7 +194,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.68.1' + artifact = 'io.grpc:protoc-gen-grpc-java:1.69.0' } } generateProtoTasks { From 86b9529befe81123a1b870f1c855a038af121861 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Fri, 20 Dec 2024 17:37:04 +0000 Subject: [PATCH 43/70] :Revert "Update README etc to reference 1.69.0" This reverts commit f5b3614c6e3e7e3d7494988bd5be00619a4bbf53. --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c6a8f3bdd8a..97b2bd6d5f9 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ For a guided tour, take a look at the [quick start guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC basics](https://grpc.io/docs/languages/java/basics). -The [examples](https://github.com/grpc/grpc-java/tree/v1.69.0/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.69.0/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.68.1/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.68.1/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.69.0 + 1.68.1 runtime io.grpc grpc-protobuf - 1.69.0 + 1.68.1 io.grpc grpc-stub - 1.69.0 + 1.68.1 org.apache.tomcat @@ -79,18 +79,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: Or for Gradle with non-Android, add to your dependencies: ```gradle -runtimeOnly 'io.grpc:grpc-netty-shaded:1.69.0' -implementation 'io.grpc:grpc-protobuf:1.69.0' -implementation 'io.grpc:grpc-stub:1.69.0' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.68.1' +implementation 'io.grpc:grpc-protobuf:1.68.1' +implementation 'io.grpc:grpc-stub:1.68.1' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and `grpc-protobuf-lite` instead of `grpc-protobuf`: ```gradle -implementation 'io.grpc:grpc-okhttp:1.69.0' -implementation 'io.grpc:grpc-protobuf-lite:1.69.0' -implementation 'io.grpc:grpc-stub:1.69.0' +implementation 'io.grpc:grpc-okhttp:1.68.1' +implementation 'io.grpc:grpc-protobuf-lite:1.68.1' +implementation 'io.grpc:grpc-stub:1.68.1' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` @@ -99,7 +99,7 @@ For [Bazel](https://bazel.build), you can either (with the GAVs from above), or use `@io_grpc_grpc_java//api` et al (see below). [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.69.0 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.68.1 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -131,7 +131,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.69.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.68.1:exe:${os.detected.classifier} @@ -161,7 +161,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.69.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.68.1' } } generateProtoTasks { @@ -194,7 +194,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.69.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.68.1' } } generateProtoTasks { From 60efd8475068453e299ed00063914175385b06a7 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Tue, 7 Jan 2025 18:40:48 +0530 Subject: [PATCH 44/70] Fix style. --- netty/src/main/java/io/grpc/netty/NettyClientTransport.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index b72bb35c74a..d16b3ad9a43 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -200,8 +200,8 @@ public ClientStream newStream( if (GrpcUtil.getFlag("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", false)) { return new FailingClientStream(verificationStatus, tracers); } - channelLogger.log(ChannelLogger.ChannelLogLevel.WARNING, "Authority '{}' specified via call" + - " options for rpc did not match peer certificate's subject names."); + channelLogger.log(ChannelLogger.ChannelLogLevel.WARNING, "Authority '{}' specified via " + + "call options for rpc did not match peer certificate's subject names."); } } StatsTraceContext statsTraceCtx = From 60b1ee0461ff02c235c2462c181916575f610a66 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Tue, 7 Jan 2025 18:59:33 +0530 Subject: [PATCH 45/70] Fix style. --- .../src/test/java/io/grpc/okhttp/TlsTest.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java index 67901a9db3c..e37f3286d95 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java @@ -427,7 +427,8 @@ public X509Certificate[] getAcceptedIssuers() { } } - /** Used to capture the fact that checkServerTrusted has been called for the per-rpc authority verification. */ + /** Used to capture the fact that checkServerTrusted has been called for the per-rpc authority + * verification. */ private static class FakeX509ExtendedTrustManager extends X509ExtendedTrustManager { private final X509ExtendedTrustManager delegate; private boolean checkServerTrustedCalled; @@ -437,32 +438,34 @@ private FakeX509ExtendedTrustManager(X509ExtendedTrustManager delegate) { } @Override - public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) + throws CertificateException { delegate.checkServerTrusted(chain, authType, socket); this.checkServerTrustedCalled = true; } @Override - public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { + public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) + throws CertificateException { + delegate.checkServerTrusted(chain, authType, engine); } @Override - public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { - delegate.checkServerTrusted(chain, authType, engine); + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + delegate.checkServerTrusted(chain, authType); } @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) { + } + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - delegate.checkServerTrusted(chain, authType); + public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) { } @Override From 25948423ae5708a7ba6ba76a581a3237d3c45c87 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Tue, 7 Jan 2025 19:37:39 +0530 Subject: [PATCH 46/70] Fix merge conflicts. --- netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java | 1 - .../src/test/java/io/grpc/netty/NettyClientTransportTest.java | 2 +- .../src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 454ead13cb1..940c42dd4e6 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -83,7 +83,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Level; diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 3582a31a28e..9ab078c07f6 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -967,7 +967,7 @@ private ProtocolNegotiator newNegotiator() throws IOException, GeneralSecurityEx TlsTesting.loadCert("ca.pem")).get()); } - private static Optional getX509ExtendedTrustManager(InputStream rootCerts) + private static java.util.Optional getX509ExtendedTrustManager(InputStream rootCerts) throws GeneralSecurityException { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); try { diff --git a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java index 48b5b895b8d..4532c5c056b 100644 --- a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java +++ b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java @@ -1034,7 +1034,7 @@ public void clientTlsHandler_closeDuringNegotiation() throws Exception { private ClientTlsProtocolNegotiator getClientTlsProtocolNegotiator() throws SSLException { return new ClientTlsProtocolNegotiator(GrpcSslContexts.forClient().trustManager( TlsTesting.loadCert("ca.pem")).build(), - null, Optional.empty(), null); + null, Optional.absent(), null); } @Test @@ -1043,7 +1043,7 @@ public void allowedAuthoritiesForTransport_LruCache() throws SSLException, Certi ClientTlsProtocolNegotiator negotiator = new ClientTlsProtocolNegotiator( GrpcSslContexts.forClient().trustManager( TlsTesting.loadCert("ca.pem")).build(), - null, Optional.empty(), mockX509ExtendedTrustManager); + null, Optional.absent(), mockX509ExtendedTrustManager); SSLEngine mockSslEngine = mock(SSLEngine.class); negotiator.setSslEngine(mockSslEngine); SSLSession mockSslSession = mock(SSLSession.class); From 916d0d57cf5670f3be7efcf6131435def44bf27a Mon Sep 17 00:00:00 2001 From: deadEternally Date: Fri, 10 Jan 2025 21:35:00 +0530 Subject: [PATCH 47/70] Review comments and use reflection for X509ExtendedTrustManager. --- .../io/grpc/netty/NettyClientTransport.java | 10 +- .../io/grpc/netty/ProtocolNegotiators.java | 99 ++++++++++++------- .../grpc/netty/NettyClientTransportTest.java | 27 +++-- 3 files changed, 91 insertions(+), 45 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index d16b3ad9a43..7d484ee47dd 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -63,6 +63,7 @@ import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; import javax.annotation.Nullable; /** @@ -70,6 +71,8 @@ */ class NettyClientTransport implements ConnectionClientTransport { + private static final boolean enablePerRpcAuthorityCheck = + GrpcUtil.getFlag("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", false); private final InternalLogId logId; private final Map, ?> channelOptions; private final SocketAddress remoteAddress; @@ -105,6 +108,7 @@ class NettyClientTransport implements ConnectionClientTransport { private final ChannelLogger channelLogger; private final boolean useGetForSafeMethods; private final Ticker ticker; + private final Logger logger = Logger.getLogger(NettyClientTransport.class.getName()); NettyClientTransport( SocketAddress address, @@ -197,11 +201,11 @@ public ClientStream newStream( if (callOptions.getAuthority() != null) { Status verificationStatus = negotiator.verifyAuthority(callOptions.getAuthority()); if (!verificationStatus.isOk()) { - if (GrpcUtil.getFlag("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", false)) { + if (enablePerRpcAuthorityCheck) { return new FailingClientStream(verificationStatus, tracers); } - channelLogger.log(ChannelLogger.ChannelLogLevel.WARNING, "Authority '{}' specified via " - + "call options for rpc did not match peer certificate's subject names."); + logger.warning("Authority verification for the rpc failed (This will be an error in the " + + "future) with error status: " + verificationStatus.getDescription()); } } StatsTraceContext statsTraceCtx = diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 940c42dd4e6..42f4880b6df 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -47,6 +47,7 @@ import io.grpc.internal.GrpcUtil; import io.grpc.internal.NoopSslSession; import io.grpc.internal.ObjectPool; +import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; @@ -61,6 +62,7 @@ import io.netty.handler.codec.http2.Http2ClientUpgradeCodec; import io.netty.handler.proxy.HttpProxyHandler; import io.netty.handler.proxy.ProxyConnectionEvent; +import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.OpenSslEngine; import io.netty.handler.ssl.SslContext; @@ -70,6 +72,8 @@ import io.netty.handler.ssl.SslProvider; import io.netty.util.AsciiString; import java.io.ByteArrayInputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.SocketAddress; import java.net.URI; import java.nio.channels.ClosedChannelException; @@ -97,7 +101,7 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedTrustManager; +import javax.net.ssl.X509TrustManager; import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; /** @@ -111,7 +115,16 @@ final class ProtocolNegotiators { private static final EnumSet understoodServerTlsFeatures = EnumSet.of( TlsServerCredentials.Feature.MTLS, TlsServerCredentials.Feature.CUSTOM_MANAGERS); - + private static Class x509ExtendedTrustManagerClass; + private static final Logger logger = Logger.getLogger(ProtocolNegotiators.class.getName()); + static { + try { + x509ExtendedTrustManagerClass = Class.forName("javax.net.ssl.X509ExtendedTrustManager"); + } catch (ClassNotFoundException e) { + logger.info("javax.net.ssl.X509ExtendedTrustManager is not available. Authority override via call options" + + "via call options will not be allowed."); + } + } private ProtocolNegotiators() { } @@ -149,14 +162,16 @@ public static FromChannelCredentialsResult from(ChannelCredentials creds) { } builder.trustManager(new FixedTrustManagerFactory(trustManagers)); TrustManager x509ExtendedTrustManager = null; - for (TrustManager trustManager: trustManagers) { - if (trustManager instanceof X509ExtendedTrustManager) { - x509ExtendedTrustManager = trustManager; - break; + if (x509ExtendedTrustManagerClass != null) { + for (TrustManager trustManager : trustManagers) { + if (x509ExtendedTrustManagerClass.isInstance(trustManager)) { + x509ExtendedTrustManager = trustManager; + break; + } } } return FromChannelCredentialsResult.negotiator(tlsClientFactory(builder.build(), - (X509ExtendedTrustManager) x509ExtendedTrustManager)); + (X509TrustManager) x509ExtendedTrustManager)); } catch (SSLException | GeneralSecurityException ex) { log.log(Level.FINE, "Exception building SslContext", ex); return FromChannelCredentialsResult.error( @@ -222,15 +237,15 @@ public static FromServerCredentialsResult from(ServerCredentials creds) { } // else use system default switch (tlsCreds.getClientAuth()) { case OPTIONAL: - builder.clientAuth(io.netty.handler.ssl.ClientAuth.OPTIONAL); + builder.clientAuth(ClientAuth.OPTIONAL); break; case REQUIRE: - builder.clientAuth(io.netty.handler.ssl.ClientAuth.REQUIRE); + builder.clientAuth(ClientAuth.REQUIRE); break; case NONE: - builder.clientAuth(io.netty.handler.ssl.ClientAuth.NONE); + builder.clientAuth(ClientAuth.NONE); break; default: @@ -286,17 +301,17 @@ private FromChannelCredentialsResult(ProtocolNegotiator.ClientFactory negotiator public static FromChannelCredentialsResult error(String error) { return new FromChannelCredentialsResult( - null, null, Preconditions.checkNotNull(error, "error")); + null, null, checkNotNull(error, "error")); } public static FromChannelCredentialsResult negotiator( ProtocolNegotiator.ClientFactory factory) { return new FromChannelCredentialsResult( - Preconditions.checkNotNull(factory, "factory"), null, null); + checkNotNull(factory, "factory"), null, null); } public FromChannelCredentialsResult withCallCredentials(CallCredentials callCreds) { - Preconditions.checkNotNull(callCreds, "callCreds"); + checkNotNull(callCreds, "callCreds"); if (error != null) { return this; } @@ -317,11 +332,11 @@ private FromServerCredentialsResult(ProtocolNegotiator.ServerFactory negotiator, } public static FromServerCredentialsResult error(String error) { - return new FromServerCredentialsResult(null, Preconditions.checkNotNull(error, "error")); + return new FromServerCredentialsResult(null, checkNotNull(error, "error")); } public static FromServerCredentialsResult negotiator(ProtocolNegotiator.ServerFactory factory) { - return new FromServerCredentialsResult(Preconditions.checkNotNull(factory, "factory"), null); + return new FromServerCredentialsResult(checkNotNull(factory, "factory"), null); } } @@ -336,7 +351,7 @@ private static final class FixedProtocolNegotiatorServerFactory public FixedProtocolNegotiatorServerFactory(ProtocolNegotiator protocolNegotiator) { this.protocolNegotiator = - Preconditions.checkNotNull(protocolNegotiator, "protocolNegotiator"); + checkNotNull(protocolNegotiator, "protocolNegotiator"); } @Override @@ -378,7 +393,7 @@ static final class TlsProtocolNegotiatorServerFactory private final SslContext sslContext; public TlsProtocolNegotiatorServerFactory(SslContext sslContext) { - this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext"); + this.sslContext = checkNotNull(sslContext, "sslContext"); } @Override @@ -393,7 +408,7 @@ public ProtocolNegotiator newNegotiator(ObjectPool offloadEx */ public static ProtocolNegotiator serverTls(final SslContext sslContext, final ObjectPool executorPool) { - Preconditions.checkNotNull(sslContext, "sslContext"); + checkNotNull(sslContext, "sslContext"); final Executor executor; if (executorPool != null) { // The handlers here can out-live the {@link ProtocolNegotiator}. @@ -575,6 +590,20 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws } static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { + private static final Logger logger = Logger.getLogger(ClientTlsProtocolNegotiator.class.getName()); + private static Method checkServerTrustedMethod; + static { + try { + Class x509ExtendedTrustManagerClass = Class.forName("javax.net.ssl.X509ExtendedTrustManager"); + checkServerTrustedMethod = x509ExtendedTrustManagerClass.getMethod("checkServerTrusted", + X509Certificate[].class, String.class, SSLEngine.class); + } catch (ClassNotFoundException e) { + } catch (NoSuchMethodException e) { + // Should never happen. + logger.warning("Method checkServerTrusted not found in javax.net.ssl.X509ExtendedTrustManager"); + } + } + @GuardedBy("this") private final LinkedHashMap peerVerificationResults = new LinkedHashMap() { @@ -587,7 +616,7 @@ protected boolean removeEldestEntry(Map.Entry eldest) { public ClientTlsProtocolNegotiator(SslContext sslContext, ObjectPool executorPool, Optional handshakeCompleteRunnable, - X509ExtendedTrustManager x509ExtendedTrustManager) { + X509TrustManager x509ExtendedTrustManager) { this.sslContext = checkNotNull(sslContext, "sslContext"); this.executorPool = executorPool; if (this.executorPool != null) { @@ -600,7 +629,7 @@ public ClientTlsProtocolNegotiator(SslContext sslContext, private final SslContext sslContext; private final ObjectPool executorPool; private final Optional handshakeCompleteRunnable; - private final X509ExtendedTrustManager x509ExtendedTrustManager; + private final X509TrustManager x509ExtendedTrustManager; private Executor executor; @Override @@ -640,7 +669,7 @@ public synchronized Status verifyAuthority(@Nonnull String authority) { try { verifyAuthorityAllowedForPeerCert(authority); peerVerificationStatus = Status.OK; - } catch (SSLPeerUnverifiedException | CertificateException e) { + } catch (SSLPeerUnverifiedException | CertificateException | InvocationTargetException | IllegalAccessException e) { peerVerificationStatus = Status.UNAVAILABLE.withDescription( String.format("Peer hostname verification during rpc failed for authority '%s'", authority)).withCause(e); @@ -655,7 +684,11 @@ public void setSslEngine(SSLEngine sslEngine) { } private void verifyAuthorityAllowedForPeerCert(String authority) - throws SSLPeerUnverifiedException, CertificateException { + throws SSLPeerUnverifiedException, CertificateException, InvocationTargetException, + IllegalAccessException { + if (checkServerTrustedMethod == null) { + throw new IllegalStateException("Method checkServerTrusted not found in javax.net.ssl.X509ExtendedTrustManager"); + } SSLEngine sslEngineWrapper = new SslEngineWrapper(sslEngine, authority); // The typecasting of Certificate to X509Certificate should work because this method will only // be called when using TLS and thus X509. @@ -664,7 +697,7 @@ private void verifyAuthorityAllowedForPeerCert(String authority) for (int i = 0; i < peerCertificates.length; i++) { x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; } - x509ExtendedTrustManager.checkServerTrusted(x509PeerCertificates, "RSA", sslEngineWrapper); + checkServerTrustedMethod.invoke(x509ExtendedTrustManager, x509PeerCertificates, "RSA", sslEngineWrapper); } @VisibleForTesting @@ -768,7 +801,7 @@ private void propagateTlsComplete(ChannelHandlerContext ctx, SSLSession session) @VisibleForTesting static HostPort parseAuthority(String authority) { - URI uri = GrpcUtil.authorityToUri(Preconditions.checkNotNull(authority, "authority")); + URI uri = GrpcUtil.authorityToUri(checkNotNull(authority, "authority")); String host; int port; if (uri.getHost() != null) { @@ -791,30 +824,30 @@ static HostPort parseAuthority(String authority) { /** * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will - * be negotiated, the {@code handler} is added and writes to the {@link io.netty.channel.Channel} + * be negotiated, the {@code handler} is added and writes to the {@link Channel} * may happen immediately, even before the TLS Handshake is complete. * @param executorPool a dedicated {@link Executor} pool for time-consuming TLS tasks */ public static ProtocolNegotiator tls(SslContext sslContext, ObjectPool executorPool, Optional handshakeCompleteRunnable, - X509ExtendedTrustManager x509ExtendedTrustManager) { + X509TrustManager x509ExtendedTrustManager) { return new ClientTlsProtocolNegotiator(sslContext, executorPool, handshakeCompleteRunnable, x509ExtendedTrustManager); } /** * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will - * be negotiated, the {@code handler} is added and writes to the {@link io.netty.channel.Channel} + * be negotiated, the {@code handler} is added and writes to the {@link Channel} * may happen immediately, even before the TLS Handshake is complete. */ public static ProtocolNegotiator tls(SslContext sslContext, - X509ExtendedTrustManager x509ExtendedTrustManager) { + X509TrustManager x509ExtendedTrustManager) { return tls(sslContext, null, Optional.absent(), x509ExtendedTrustManager); } public static ProtocolNegotiator.ClientFactory tlsClientFactory(SslContext sslContext, - X509ExtendedTrustManager x509ExtendedTrustManager) { + X509TrustManager x509ExtendedTrustManager) { return new TlsProtocolNegotiatorClientFactory(sslContext, x509ExtendedTrustManager); } @@ -822,11 +855,11 @@ public static ProtocolNegotiator.ClientFactory tlsClientFactory(SslContext sslCo static final class TlsProtocolNegotiatorClientFactory implements ProtocolNegotiator.ClientFactory { private final SslContext sslContext; - private final X509ExtendedTrustManager x509ExtendedTrustManager; + private final X509TrustManager x509ExtendedTrustManager; public TlsProtocolNegotiatorClientFactory(SslContext sslContext, - X509ExtendedTrustManager x509ExtendedTrustManager) { - this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext"); + X509TrustManager x509ExtendedTrustManager) { + this.sslContext = checkNotNull(sslContext, "sslContext"); this.x509ExtendedTrustManager = x509ExtendedTrustManager; } @@ -951,7 +984,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc /** * Returns a {@link ChannelHandler} that ensures that the {@code handler} is added to the - * pipeline writes to the {@link io.netty.channel.Channel} may happen immediately, even before it + * pipeline writes to the {@link Channel} may happen immediately, even before it * is active. */ public static ProtocolNegotiator plaintext() { diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 9ab078c07f6..6be42ea7c6c 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -110,7 +110,6 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -125,7 +124,6 @@ import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509TrustManager; import javax.security.auth.x500.X500Principal; import org.junit.After; @@ -146,6 +144,15 @@ public class NettyClientTransportTest { @Rule public final MockitoRule mocks = MockitoJUnit.rule(); private static final SslContext SSL_CONTEXT = createSslContext(); + private static final Class x509ExtendedTrustManagerClass; + + static { + try { + x509ExtendedTrustManagerClass = Class.forName("javax.net.ssl.X509ExtendedTrustManager"); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } @Mock private ManagedClientTransport.Listener clientTransportListener; @@ -846,8 +853,7 @@ public void authorityOverrideInCallOptions_noX509ExtendedTrustManager_newStreamC try { startServer(); InputStream caCert = TlsTesting.loadCert("ca.pem"); - X509TrustManager x509ExtendedTrustManager = - (X509TrustManager) getX509ExtendedTrustManager(caCert).get(); + X509TrustManager x509ExtendedTrustManager = (X509TrustManager) getX509ExtendedTrustManager(caCert); ProtocolNegotiators.FromChannelCredentialsResult result = ProtocolNegotiators.from(TlsChannelCredentials.newBuilder() .trustManager(new FakeTrustManager(x509ExtendedTrustManager)).build()); @@ -963,11 +969,10 @@ private ProtocolNegotiator newNegotiator() throws IOException, GeneralSecurityEx InputStream caCert = TlsTesting.loadCert("ca.pem"); SslContext clientContext = GrpcSslContexts.forClient().trustManager(caCert).build(); return ProtocolNegotiators.tls(clientContext, - (X509ExtendedTrustManager) getX509ExtendedTrustManager( - TlsTesting.loadCert("ca.pem")).get()); + (X509TrustManager) getX509ExtendedTrustManager(TlsTesting.loadCert("ca.pem"))); } - private static java.util.Optional getX509ExtendedTrustManager(InputStream rootCerts) + private static TrustManager getX509ExtendedTrustManager(InputStream rootCerts) throws GeneralSecurityException { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); try { @@ -985,8 +990,12 @@ private static java.util.Optional getX509ExtendedTrustManager(Inpu TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(ks); - return Arrays.stream(trustManagerFactory.getTrustManagers()) - .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); + for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) { + if (x509ExtendedTrustManagerClass.isInstance(trustManager)) { + return trustManager; + } + } + return null; } private NettyClientTransport newTransport(ProtocolNegotiator negotiator) { From 040035bf808b030e0cdbcb2b5af1d45a152a9522 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Mon, 13 Jan 2025 18:33:05 +0530 Subject: [PATCH 48/70] Include failed method name in the tls verification failed log message. --- netty/src/main/java/io/grpc/netty/NettyClientTransport.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index 7d484ee47dd..4cfc8fa030a 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -204,8 +204,9 @@ public ClientStream newStream( if (enablePerRpcAuthorityCheck) { return new FailingClientStream(verificationStatus, tracers); } - logger.warning("Authority verification for the rpc failed (This will be an error in the " - + "future) with error status: " + verificationStatus.getDescription()); + logger.warning(String.format("Authority verification for the rpc %s failed (this will be an" + + " error in the future) with error status: %s", method.getFullMethodName(), + verificationStatus.getDescription())); } } StatsTraceContext statsTraceCtx = From ecbf7b7e5f6326bd6629878f3cee0f1907fe46ec Mon Sep 17 00:00:00 2001 From: deadEternally Date: Mon, 13 Jan 2025 19:57:43 +0530 Subject: [PATCH 49/70] Use reflection to access X509ExtendedTrustManager. --- .../io/grpc/okhttp/OkHttpClientTransport.java | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 04a241ad42c..49592d5836f 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -77,13 +77,14 @@ import io.perfmark.PerfMark; import java.io.EOFException; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.Socket; import java.net.URI; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.cert.Certificate; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.Deque; @@ -116,7 +117,6 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedTrustManager; import okio.Buffer; import okio.BufferedSink; import okio.BufferedSource; @@ -168,6 +168,23 @@ private static Map buildErrorCodeToStatusMap() { return Collections.unmodifiableMap(errorToStatus); } + private static Class x509ExtendedTrustManagerClass; + private static Method checkServerTrustedMethod; + + static { + try { + x509ExtendedTrustManagerClass = Class.forName("javax.net.ssl.X509ExtendedTrustManager"); + checkServerTrustedMethod = x509ExtendedTrustManagerClass.getMethod("checkServerTrusted", + X509Certificate[].class, String.class, Socket.class); + } catch (ClassNotFoundException e) { + // Per-rpc authority override via call options will be disallowed. + } catch (NoSuchMethodException e) { + // Should never happen. + Logger.getLogger(OkHttpClientTransport.class.getName()).warning("Method checkServerTrusted " + + "not found in javax.net.ssl.X509ExtendedTrustManager"); + } + } + private final InetSocketAddress address; private final String defaultAuthority; private final String userAgent; @@ -453,8 +470,8 @@ public ClientStream newStream( TrustManager x509ExtendedTrustManager = null; boolean warningLogged = false; try { - x509ExtendedTrustManager = getX509ExtendedTrustManager( - (TlsChannelCredentials) channelCredentials); + x509ExtendedTrustManager = x509ExtendedTrustManagerClass != null + ? getX509ExtendedTrustManager((TlsChannelCredentials) channelCredentials) : null; } catch (GeneralSecurityException e) { if (GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false)) { return new FailingClientStream(Status.UNAVAILABLE.withDescription( @@ -482,11 +499,18 @@ public ClientStream newStream( for (int i = 0; i < peerCertificates.length; i++) { x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; } - ((X509ExtendedTrustManager) x509ExtendedTrustManager).checkServerTrusted( - x509PeerCertificates, "RSA", - new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); - peerVerificationStatus = Status.OK; - } catch (SSLPeerUnverifiedException | CertificateException e) { + // Should never happen + if (checkServerTrustedMethod == null) { + peerVerificationStatus = Status.UNAVAILABLE.withDescription( + "Method checkServerTrusted not found in " + + "javax.net.ssl.X509ExtendedTrustManager"); + } else { + checkServerTrustedMethod.invoke(x509ExtendedTrustManager, x509PeerCertificates, + "RSA", new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); + peerVerificationStatus = Status.OK; + } + } catch (SSLPeerUnverifiedException | InvocationTargetException + | IllegalAccessException e) { peerVerificationStatus = Status.UNAVAILABLE.withDescription( String.format("Failure in verifying authority '%s' against peer during rpc", callOptions.getAuthority())).withCause(e); @@ -533,7 +557,7 @@ private TrustManager getX509ExtendedTrustManager(TlsChannelCredentials tlsCreds) tm = tmf.getTrustManagers(); } for (TrustManager trustManager: tm) { - if (trustManager instanceof X509ExtendedTrustManager) { + if (x509ExtendedTrustManagerClass.isInstance(trustManager)) { return trustManager; } } From 44f2412b62cf47f5baa02c9e2b14e0aa660b27ac Mon Sep 17 00:00:00 2001 From: deadEternally Date: Wed, 15 Jan 2025 17:56:54 +0530 Subject: [PATCH 50/70] Address review comments. --- .../io/grpc/netty/NettyClientTransport.java | 4 ---- .../io/grpc/netty/ProtocolNegotiators.java | 19 ++++++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index 4cfc8fa030a..5e9cf85fef0 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -108,7 +108,6 @@ class NettyClientTransport implements ConnectionClientTransport { private final ChannelLogger channelLogger; private final boolean useGetForSafeMethods; private final Ticker ticker; - private final Logger logger = Logger.getLogger(NettyClientTransport.class.getName()); NettyClientTransport( SocketAddress address, @@ -204,9 +203,6 @@ public ClientStream newStream( if (enablePerRpcAuthorityCheck) { return new FailingClientStream(verificationStatus, tracers); } - logger.warning(String.format("Authority verification for the rpc %s failed (this will be an" + - " error in the future) with error status: %s", method.getFullMethodName(), - verificationStatus.getDescription())); } } StatsTraceContext statsTraceCtx = diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 42f4880b6df..72ba791e8d3 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -116,13 +116,12 @@ final class ProtocolNegotiators { EnumSet.of( TlsServerCredentials.Feature.MTLS, TlsServerCredentials.Feature.CUSTOM_MANAGERS); private static Class x509ExtendedTrustManagerClass; - private static final Logger logger = Logger.getLogger(ProtocolNegotiators.class.getName()); + static { try { x509ExtendedTrustManagerClass = Class.forName("javax.net.ssl.X509ExtendedTrustManager"); } catch (ClassNotFoundException e) { - logger.info("javax.net.ssl.X509ExtendedTrustManager is not available. Authority override via call options" + - "via call options will not be allowed."); + // Will disallow per-rpc authority override via call option. } } @@ -591,17 +590,20 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { private static final Logger logger = Logger.getLogger(ClientTlsProtocolNegotiator.class.getName()); - private static Method checkServerTrustedMethod; + private static final Method checkServerTrustedMethod; static { + Method method = null; try { Class x509ExtendedTrustManagerClass = Class.forName("javax.net.ssl.X509ExtendedTrustManager"); - checkServerTrustedMethod = x509ExtendedTrustManagerClass.getMethod("checkServerTrusted", + method = x509ExtendedTrustManagerClass.getMethod("checkServerTrusted", X509Certificate[].class, String.class, SSLEngine.class); } catch (ClassNotFoundException e) { } catch (NoSuchMethodException e) { // Should never happen. - logger.warning("Method checkServerTrusted not found in javax.net.ssl.X509ExtendedTrustManager"); + logger.log(Level.WARNING, "Method checkServerTrusted not found in " + + "javax.net.ssl.X509ExtendedTrustManager", e); } + checkServerTrustedMethod = method; } @GuardedBy("this") @@ -669,10 +671,13 @@ public synchronized Status verifyAuthority(@Nonnull String authority) { try { verifyAuthorityAllowedForPeerCert(authority); peerVerificationStatus = Status.OK; - } catch (SSLPeerUnverifiedException | CertificateException | InvocationTargetException | IllegalAccessException e) { + } catch (SSLPeerUnverifiedException | CertificateException | InvocationTargetException | + IllegalAccessException | IllegalStateException e) { peerVerificationStatus = Status.UNAVAILABLE.withDescription( String.format("Peer hostname verification during rpc failed for authority '%s'", authority)).withCause(e); + logger.log(Level.WARNING, "Authority verification failed (this will be an error in the " + + "future).", e); } peerVerificationResults.put(authority, peerVerificationStatus); return peerVerificationStatus; From 6449728c798a203826ddbff37dffb91c70ab151e Mon Sep 17 00:00:00 2001 From: deadEternally Date: Thu, 16 Jan 2025 23:11:28 +0530 Subject: [PATCH 51/70] Address review comments. --- .../io/grpc/netty/NettyClientTransport.java | 26 ++++++-- .../io/grpc/netty/ProtocolNegotiators.java | 59 ++++++++----------- .../grpc/netty/NettyClientTransportTest.java | 15 +++-- .../grpc/netty/ProtocolNegotiatorsTest.java | 43 -------------- 4 files changed, 54 insertions(+), 89 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index 5e9cf85fef0..e67629cacd9 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -60,9 +60,11 @@ import io.netty.util.concurrent.GenericFutureListener; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; +import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -71,8 +73,6 @@ */ class NettyClientTransport implements ConnectionClientTransport { - private static final boolean enablePerRpcAuthorityCheck = - GrpcUtil.getFlag("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", false); private final InternalLogId logId; private final Map, ?> channelOptions; private final SocketAddress remoteAddress; @@ -108,6 +108,14 @@ class NettyClientTransport implements ConnectionClientTransport { private final ChannelLogger channelLogger; private final boolean useGetForSafeMethods; private final Ticker ticker; + private final Logger logger = Logger.getLogger(NettyClientTransport.class.getName()); + private final LinkedHashMap peerVerificationResults = + new LinkedHashMap() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > 100; + } + }; NettyClientTransport( SocketAddress address, @@ -198,9 +206,19 @@ public ClientStream newStream( return new FailingClientStream(statusExplainingWhyTheChannelIsNull, tracers); } if (callOptions.getAuthority() != null) { - Status verificationStatus = negotiator.verifyAuthority(callOptions.getAuthority()); + Status verificationStatus = peerVerificationResults.get(callOptions.getAuthority()); + if (verificationStatus == null) { + verificationStatus = negotiator.verifyAuthority(callOptions.getAuthority()); + if (!verificationStatus.isOk()) { + logger.log(Level.WARNING, String.format("Peer hostname verification during rpc failed " + + "for authority '%s' for method '%s' with the error \"%s\". This will " + + "be an error in the future.", callOptions.getAuthority(), + method.getFullMethodName(), verificationStatus.getDescription()), + verificationStatus.getCause()); + } + } if (!verificationStatus.isOk()) { - if (enablePerRpcAuthorityCheck) { + if (GrpcUtil.getFlag("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", false)) { return new FailingClientStream(verificationStatus, tracers); } } diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 72ba791e8d3..93418a4ef2f 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -21,7 +21,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; -import com.google.common.base.Preconditions; import com.google.errorprone.annotations.ForOverride; import io.grpc.Attributes; import io.grpc.CallCredentials; @@ -84,16 +83,13 @@ import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.EnumSet; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import javax.annotation.concurrent.GuardedBy; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; @@ -589,31 +585,27 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws } static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { - private static final Logger logger = Logger.getLogger(ClientTlsProtocolNegotiator.class.getName()); + private static final Logger logger = + Logger.getLogger(ClientTlsProtocolNegotiator.class.getName()); private static final Method checkServerTrustedMethod; + static { Method method = null; try { - Class x509ExtendedTrustManagerClass = Class.forName("javax.net.ssl.X509ExtendedTrustManager"); + Class x509ExtendedTrustManagerClass = + Class.forName("javax.net.ssl.X509ExtendedTrustManager"); method = x509ExtendedTrustManagerClass.getMethod("checkServerTrusted", X509Certificate[].class, String.class, SSLEngine.class); } catch (ClassNotFoundException e) { + // Per-rpc authority overriding via call options will be disallowed. } catch (NoSuchMethodException e) { // Should never happen. - logger.log(Level.WARNING, "Method checkServerTrusted not found in " + - "javax.net.ssl.X509ExtendedTrustManager", e); + logger.log(Level.WARNING, "Method checkServerTrusted not found in " + + "javax.net.ssl.X509ExtendedTrustManager", e); } checkServerTrustedMethod = method; } - @GuardedBy("this") - private final LinkedHashMap peerVerificationResults = - new LinkedHashMap() { - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > 100; - } - }; private SSLEngine sslEngine; public ClientTlsProtocolNegotiator(SslContext sslContext, @@ -656,7 +648,7 @@ public void close() { } @Override - public synchronized Status verifyAuthority(@Nonnull String authority) { + public Status verifyAuthority(@Nonnull String authority) { // sslEngine won't be set when creating ClientTlsHandler from InternalProtocolNegotiators // for example. if (sslEngine == null || x509ExtendedTrustManager == null) { @@ -664,24 +656,17 @@ public synchronized Status verifyAuthority(@Nonnull String authority) { "Can't allow authority override in rpc when SslEngine or X509ExtendedTrustManager" + " is not available"); } - if (peerVerificationResults.containsKey(authority)) { - return peerVerificationResults.get(authority); - } else { - Status peerVerificationStatus; - try { - verifyAuthorityAllowedForPeerCert(authority); - peerVerificationStatus = Status.OK; - } catch (SSLPeerUnverifiedException | CertificateException | InvocationTargetException | - IllegalAccessException | IllegalStateException e) { - peerVerificationStatus = Status.UNAVAILABLE.withDescription( - String.format("Peer hostname verification during rpc failed for authority '%s'", - authority)).withCause(e); - logger.log(Level.WARNING, "Authority verification failed (this will be an error in the " - + "future).", e); - } - peerVerificationResults.put(authority, peerVerificationStatus); - return peerVerificationStatus; + Status peerVerificationStatus; + try { + verifyAuthorityAllowedForPeerCert(authority); + peerVerificationStatus = Status.OK; + } catch (SSLPeerUnverifiedException | CertificateException | InvocationTargetException + | IllegalAccessException | IllegalStateException e) { + peerVerificationStatus = Status.UNAVAILABLE.withDescription( + String.format("Peer hostname verification during rpc failed for authority '%s'", + authority)).withCause(e); } + return peerVerificationStatus; } public void setSslEngine(SSLEngine sslEngine) { @@ -692,7 +677,8 @@ private void verifyAuthorityAllowedForPeerCert(String authority) throws SSLPeerUnverifiedException, CertificateException, InvocationTargetException, IllegalAccessException { if (checkServerTrustedMethod == null) { - throw new IllegalStateException("Method checkServerTrusted not found in javax.net.ssl.X509ExtendedTrustManager"); + throw new IllegalStateException("Method checkServerTrusted not found in " + + "javax.net.ssl.X509ExtendedTrustManager"); } SSLEngine sslEngineWrapper = new SslEngineWrapper(sslEngine, authority); // The typecasting of Certificate to X509Certificate should work because this method will only @@ -702,7 +688,8 @@ private void verifyAuthorityAllowedForPeerCert(String authority) for (int i = 0; i < peerCertificates.length; i++) { x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; } - checkServerTrustedMethod.invoke(x509ExtendedTrustManager, x509PeerCertificates, "RSA", sslEngineWrapper); + checkServerTrustedMethod.invoke( + x509ExtendedTrustManager, x509PeerCertificates, "RSA", sslEngineWrapper); } @VisibleForTesting diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 6be42ea7c6c..9eeea04b2e0 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -853,7 +853,8 @@ public void authorityOverrideInCallOptions_noX509ExtendedTrustManager_newStreamC try { startServer(); InputStream caCert = TlsTesting.loadCert("ca.pem"); - X509TrustManager x509ExtendedTrustManager = (X509TrustManager) getX509ExtendedTrustManager(caCert); + X509TrustManager x509ExtendedTrustManager = + (X509TrustManager) getX509ExtendedTrustManager(caCert); ProtocolNegotiators.FromChannelCredentialsResult result = ProtocolNegotiators.from(TlsChannelCredentials.newBuilder() .trustManager(new FakeTrustManager(x509ExtendedTrustManager)).build()); @@ -874,8 +875,9 @@ Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.i InsightBuilder insightBuilder = new InsightBuilder(); stream.appendTimeoutInsight(insightBuilder); assertThat(insightBuilder.toString()).contains( - "Status{code=FAILED_PRECONDITION, description=Can't allow authority override in rpc when " - + "SslEngine or X509ExtendedTrustManager is not available, cause=null}"); + "Status{code=FAILED_PRECONDITION, description=Can't allow authority override in rpc " + + "when SslEngine or X509ExtendedTrustManager is not available, " + + "cause=null}"); } finally { System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); } @@ -904,9 +906,10 @@ Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.i InsightBuilder insightBuilder = new InsightBuilder(); stream.appendTimeoutInsight(insightBuilder); assertThat(insightBuilder.toString()).contains( - "Status{code=UNAVAILABLE, description=Peer hostname verification during rpc failed for" - + " authority 'foo.test.google.in'"); - assertThat(insightBuilder.toString()).contains("cause=java.security.cert.CertificateException:" + "Status{code=UNAVAILABLE, description=Peer hostname verification during rpc failed " + + "for authority 'foo.test.google.in'"); + assertThat(insightBuilder.toString()).contains( + "Caused by: java.security.cert.CertificateException:" + " No subject alternative DNS name matching foo.test.google.in found."); } finally { System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); diff --git a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java index 4532c5c056b..1e5a454480b 100644 --- a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java +++ b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java @@ -26,12 +26,10 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import com.google.common.base.Optional; import io.grpc.Attributes; @@ -69,7 +67,6 @@ import io.grpc.netty.ProtocolNegotiators.ClientTlsProtocolNegotiator; import io.grpc.netty.ProtocolNegotiators.HostPort; import io.grpc.netty.ProtocolNegotiators.ServerTlsHandler; -import io.grpc.netty.ProtocolNegotiators.SslEngineWrapper; import io.grpc.netty.ProtocolNegotiators.WaitUntilActiveHandler; import io.grpc.testing.TlsTesting; import io.grpc.util.CertificateUtils; @@ -122,7 +119,6 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayDeque; @@ -144,7 +140,6 @@ import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedTrustManager; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; @@ -1037,44 +1032,6 @@ private ClientTlsProtocolNegotiator getClientTlsProtocolNegotiator() throws SSLE null, Optional.absent(), null); } - @Test - public void allowedAuthoritiesForTransport_LruCache() throws SSLException, CertificateException { - X509ExtendedTrustManager mockX509ExtendedTrustManager = mock(X509ExtendedTrustManager.class); - ClientTlsProtocolNegotiator negotiator = new ClientTlsProtocolNegotiator( - GrpcSslContexts.forClient().trustManager( - TlsTesting.loadCert("ca.pem")).build(), - null, Optional.absent(), mockX509ExtendedTrustManager); - SSLEngine mockSslEngine = mock(SSLEngine.class); - negotiator.setSslEngine(mockSslEngine); - SSLSession mockSslSession = mock(SSLSession.class); - when(mockSslEngine.getSession()).thenReturn(mockSslSession); - when(mockSslSession.getPeerCertificates()).thenReturn(new Certificate[0]); - - // Fill the cache - for (int i = 0; i < 100; i++) { - Status unused = negotiator.verifyAuthority("authority" + i); - } - // Should use value from the cache. - Status unused = negotiator.verifyAuthority("authority0"); - // Should evict authority0. - unused = negotiator.verifyAuthority("authority100"); - // Should call TrustManager as the cached value has been evicted for this authority value. - unused = negotiator.verifyAuthority("authority0"); - - ArgumentCaptor sslEngineWrapperArgumentCaptor = - ArgumentCaptor.forClass(SslEngineWrapper.class); - verify(mockX509ExtendedTrustManager, times(102)).checkServerTrusted(any(), eq("RSA"), - sslEngineWrapperArgumentCaptor.capture()); - List sslEngineWrappersCaptured = - sslEngineWrapperArgumentCaptor.getAllValues(); - assertThat(sslEngineWrappersCaptured).hasSize(102); - for (int i = 0; i < 100; i++) { - assertThat(sslEngineWrappersCaptured.get(i).getPeerHost()).isEqualTo("authority" + i); - } - assertThat(sslEngineWrappersCaptured.get(100).getPeerHost()).isEqualTo("authority100"); - assertThat(sslEngineWrappersCaptured.get(101).getPeerHost()).isEqualTo("authority0"); - } - @Test public void engineLog() { ChannelHandler handler = new ServerTlsHandler(grpcHandler, sslContext, null); From 42734529ddf240257f9c5ba1d87b12407f4c7c60 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Fri, 17 Jan 2025 14:53:58 +0530 Subject: [PATCH 52/70] Address review comments. --- .../io/grpc/netty/NettyClientTransport.java | 12 +- .../grpc/netty/NettyClientTransportTest.java | 220 +++++++++++------- 2 files changed, 146 insertions(+), 86 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index e67629cacd9..36993b6df5a 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -60,6 +60,7 @@ import io.netty.util.concurrent.GenericFutureListener; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.Executor; @@ -109,13 +110,17 @@ class NettyClientTransport implements ConnectionClientTransport { private final boolean useGetForSafeMethods; private final Ticker ticker; private final Logger logger = Logger.getLogger(NettyClientTransport.class.getName()); - private final LinkedHashMap peerVerificationResults = + private final Map peerVerificationResults = Collections.synchronizedMap( new LinkedHashMap() { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > 100; } - }; + }); + + @VisibleForTesting + static boolean enablePerRpcAuthorityCheck = + GrpcUtil.getFlag("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", false); NettyClientTransport( SocketAddress address, @@ -209,6 +214,7 @@ public ClientStream newStream( Status verificationStatus = peerVerificationResults.get(callOptions.getAuthority()); if (verificationStatus == null) { verificationStatus = negotiator.verifyAuthority(callOptions.getAuthority()); + peerVerificationResults.put(callOptions.getAuthority(), verificationStatus); if (!verificationStatus.isOk()) { logger.log(Level.WARNING, String.format("Peer hostname verification during rpc failed " + "for authority '%s' for method '%s' with the error \"%s\". This will " @@ -218,7 +224,7 @@ public ClientStream newStream( } } if (!verificationStatus.isOk()) { - if (GrpcUtil.getFlag("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", false)) { + if (enablePerRpcAuthorityCheck) { return new FailingClientStream(verificationStatus, tracers); } } diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 9eeea04b2e0..5394939c66a 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -37,6 +37,9 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.common.base.Optional; @@ -61,11 +64,9 @@ import io.grpc.internal.ClientStream; import io.grpc.internal.ClientStreamListener; import io.grpc.internal.ClientTransport; -import io.grpc.internal.FailingClientStream; import io.grpc.internal.FakeClock; import io.grpc.internal.FixedObjectPool; import io.grpc.internal.GrpcUtil; -import io.grpc.internal.InsightBuilder; import io.grpc.internal.ManagedClientTransport; import io.grpc.internal.ServerListener; import io.grpc.internal.ServerStream; @@ -102,6 +103,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; @@ -120,18 +122,23 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509TrustManager; import javax.security.auth.x500.X500Principal; +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.AdditionalAnswers; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -140,19 +147,11 @@ * Tests for {@link NettyClientTransport}. */ @RunWith(JUnit4.class) +@IgnoreJRERequirement public class NettyClientTransportTest { @Rule public final MockitoRule mocks = MockitoJUnit.rule(); private static final SslContext SSL_CONTEXT = createSslContext(); - private static final Class x509ExtendedTrustManagerClass; - - static { - try { - x509ExtendedTrustManagerClass = Class.forName("javax.net.ssl.X509ExtendedTrustManager"); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } @Mock private ManagedClientTransport.Listener clientTransportListener; @@ -848,8 +847,9 @@ public void tlsNegotiationServerExecutorShouldSucceed() throws Exception { */ @Test public void authorityOverrideInCallOptions_noX509ExtendedTrustManager_newStreamCreationFails() - throws IOException, InterruptedException, GeneralSecurityException { - System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); + throws IOException, InterruptedException, GeneralSecurityException, ExecutionException, + TimeoutException { + NettyClientTransport.enablePerRpcAuthorityCheck = true; try { startServer(); InputStream caCert = TlsTesting.loadCert("ca.pem"); @@ -859,106 +859,144 @@ public void authorityOverrideInCallOptions_noX509ExtendedTrustManager_newStreamC ProtocolNegotiators.from(TlsChannelCredentials.newBuilder() .trustManager(new FakeTrustManager(x509ExtendedTrustManager)).build()); NettyClientTransport transport = newTransport(result.negotiator.newNegotiator()); - FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); + SettableFuture connected = SettableFuture.create(); + FakeClientTransportListener fakeClientTransportListener = + new FakeClientTransportListener(connected); callMeMaybe(transport.start(fakeClientTransportListener)); - synchronized (fakeClientTransportListener) { - fakeClientTransportListener.wait(10000); + connected.get(10, TimeUnit.SECONDS); + assertThat(fakeClientTransportListener.isConnected()).isTrue(); + + Rpc rpc = new Rpc(transport, new Metadata(), "foo.test.google.in"); + try { + rpc.waitForClose(); + fail("Expected exception in starting stream"); + } catch (ExecutionException ex) { + Status status = ((StatusException) ex.getCause()).getStatus(); + assertThat(status.getDescription()).isEqualTo("Can't allow authority override in rpc " + + "when SslEngine or X509ExtendedTrustManager is not available"); + assertThat(status.getCode()).isEqualTo(Code.FAILED_PRECONDITION); } - assertThat(fakeClientTransportListener.isConnected).isTrue(); - - ClientStream stream = transport.newStream( - Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), - new ClientStreamTracer[]{new ClientStreamTracer() { - }}); - - assertThat(stream).isInstanceOf(FailingClientStream.class); - InsightBuilder insightBuilder = new InsightBuilder(); - stream.appendTimeoutInsight(insightBuilder); - assertThat(insightBuilder.toString()).contains( - "Status{code=FAILED_PRECONDITION, description=Can't allow authority override in rpc " - + "when SslEngine or X509ExtendedTrustManager is not available, " - + "cause=null}"); } finally { - System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); + NettyClientTransport.enablePerRpcAuthorityCheck = false; } } @Test public void authorityOverrideInCallOptions_doesntMatchServerPeerHost_newStreamCreationFails() - throws IOException, InterruptedException, GeneralSecurityException { - System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); + throws IOException, InterruptedException, GeneralSecurityException, ExecutionException, + TimeoutException { + NettyClientTransport.enablePerRpcAuthorityCheck = true; try { startServer(); NettyClientTransport transport = newTransport(newNegotiator()); - FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); + SettableFuture connected = SettableFuture.create(); + FakeClientTransportListener fakeClientTransportListener = + new FakeClientTransportListener(connected); callMeMaybe(transport.start(fakeClientTransportListener)); - synchronized (fakeClientTransportListener) { - fakeClientTransportListener.wait(10000); + connected.get(10, TimeUnit.SECONDS); + assertThat(fakeClientTransportListener.isConnected()).isTrue(); + + Rpc rpc = new Rpc(transport, new Metadata(), "foo.test.google.in"); + try { + rpc.waitForClose(); + fail("Expected exception in starting stream"); + } catch (ExecutionException ex) { + Status status = ((StatusException) ex.getCause()).getStatus(); + assertThat(status.getDescription()).isEqualTo("Peer hostname verification during rpc " + + "failed for authority 'foo.test.google.in'"); + assertThat(status.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(((InvocationTargetException) ex.getCause().getCause()).getTargetException()) + .isInstanceOf(CertificateException.class); + assertThat(((InvocationTargetException) ex.getCause().getCause()).getTargetException() + .getMessage()).isEqualTo( + "No subject alternative DNS name matching foo.test.google.in found."); } - assertThat(fakeClientTransportListener.isConnected).isTrue(); - - ClientStream stream = transport.newStream( - Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), - new ClientStreamTracer[]{new ClientStreamTracer() { - }}); - - assertThat(stream).isInstanceOf(FailingClientStream.class); - InsightBuilder insightBuilder = new InsightBuilder(); - stream.appendTimeoutInsight(insightBuilder); - assertThat(insightBuilder.toString()).contains( - "Status{code=UNAVAILABLE, description=Peer hostname verification during rpc failed " - + "for authority 'foo.test.google.in'"); - assertThat(insightBuilder.toString()).contains( - "Caused by: java.security.cert.CertificateException:" - + " No subject alternative DNS name matching foo.test.google.in found."); } finally { - System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); + NettyClientTransport.enablePerRpcAuthorityCheck = false; } } @Test public void authorityOverrideInCallOptions_matchesServerPeerHost_newStreamCreationSucceeds() - throws IOException, InterruptedException, GeneralSecurityException { - System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); + throws IOException, InterruptedException, GeneralSecurityException, ExecutionException, + TimeoutException { + NettyClientTransport.enablePerRpcAuthorityCheck = true; try { startServer(); NettyClientTransport transport = newTransport(newNegotiator()); - FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); + SettableFuture connected = SettableFuture.create(); + FakeClientTransportListener fakeClientTransportListener = + new FakeClientTransportListener(connected); callMeMaybe(transport.start(fakeClientTransportListener)); - synchronized (fakeClientTransportListener) { - fakeClientTransportListener.wait(10000); - } - assertThat(fakeClientTransportListener.isConnected).isTrue(); + connected.get(10, TimeUnit.SECONDS); + assertThat(fakeClientTransportListener.isConnected()).isTrue(); - ClientStream stream = transport.newStream( - Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("zoo.test.google.fr"), - new ClientStreamTracer[]{new ClientStreamTracer() { - }}); + new Rpc(transport, new Metadata(), "foo.test.google.fr").waitForResponse(); + } finally { + NettyClientTransport.enablePerRpcAuthorityCheck = false;; + } + } - assertThat(stream).isNotInstanceOf(FailingClientStream.class); + @Test + public void authorityOverrideInCallOptions_lruCache() + throws IOException, InterruptedException, GeneralSecurityException, ExecutionException, + TimeoutException { + NettyClientTransport.enablePerRpcAuthorityCheck = true; + try { + startServer(); + ProtocolNegotiator mockNegotiator = + mock(ProtocolNegotiator.class, AdditionalAnswers.delegatesTo(newNegotiator())); + NettyClientTransport transport = newTransport(mockNegotiator); + SettableFuture connected = SettableFuture.create(); + FakeClientTransportListener fakeClientTransportListener = + new FakeClientTransportListener(connected); + callMeMaybe(transport.start(fakeClientTransportListener)); + connected.get(10, TimeUnit.SECONDS); + assertThat(fakeClientTransportListener.isConnected()).isTrue(); + + ArgumentCaptor authorityArgumentCaptor = ArgumentCaptor.forClass(String.class); + new Rpc(transport, new Metadata(), "foo-0.test.google.fr").halfClose() + .waitForResponse(); + // Should use cache. + new Rpc(transport, new Metadata(), "foo-0.test.google.fr").waitForResponse(); + for (int i = 1; i < 100; i++) { + // Should call verifyAuthority each time here and the cache grows with each call. + new Rpc(transport, new Metadata(), "foo-" + i + ".test.google.fr").halfClose() + .waitForResponse(); + } + // Cache is full at this point. Eviction occurs here for foo-0.test.google.fr. + new Rpc(transport, new Metadata(), "foo-100.test.google.fr").halfClose() + .waitForResponse(); + // verifyAuthority call happens for foo-0.test.google.fr. + new Rpc(transport, new Metadata(), "foo-0.test.google.fr").halfClose() + .waitForResponse(); + verify(mockNegotiator, times(102)).verifyAuthority( + authorityArgumentCaptor.capture()); + List authorityValues = authorityArgumentCaptor.getAllValues(); + for (int i = 0; i < 100; i++) { + assertThat(authorityValues.get(i)).isEqualTo("foo-" + i + ".test.google.fr"); + } + assertThat(authorityValues.get(100)).isEqualTo("foo-100.test.google.fr"); + assertThat(authorityValues.get(101)).isEqualTo("foo-0.test.google.fr"); } finally { - System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); + NettyClientTransport.enablePerRpcAuthorityCheck = false;; } } @Test public void authorityOverrideInCallOptions_notMatches_flagDisabled_createsStream() - throws IOException, InterruptedException, GeneralSecurityException { + throws IOException, InterruptedException, GeneralSecurityException, ExecutionException, + TimeoutException { startServer(); NettyClientTransport transport = newTransport(newNegotiator()); - FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); + SettableFuture connected = SettableFuture.create(); + FakeClientTransportListener fakeClientTransportListener = + new FakeClientTransportListener(connected); callMeMaybe(transport.start(fakeClientTransportListener)); - synchronized (fakeClientTransportListener) { - fakeClientTransportListener.wait(10000); - } - assertThat(fakeClientTransportListener.isConnected).isTrue(); - - ClientStream stream = transport.newStream( - Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), - new ClientStreamTracer[]{new ClientStreamTracer() { - }}); + connected.get(10, TimeUnit.SECONDS); + assertThat(fakeClientTransportListener.isConnected()).isTrue(); - assertThat(stream).isInstanceOf(NettyClientStream.class); + new Rpc(transport, new Metadata(), "foo.test.google.in").waitForResponse(); } private Throwable getRootCause(Throwable t) { @@ -994,7 +1032,7 @@ private static TrustManager getX509ExtendedTrustManager(InputStream rootCerts) TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(ks); for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) { - if (x509ExtendedTrustManagerClass.isInstance(trustManager)) { + if (trustManager instanceof X509ExtendedTrustManager) { return trustManager; } } @@ -1117,12 +1155,17 @@ private static class Rpc { final TestClientStreamListener listener = new TestClientStreamListener(); Rpc(NettyClientTransport transport) { - this(transport, new Metadata()); + this(transport, new Metadata(), null); } Rpc(NettyClientTransport transport, Metadata headers) { + this(transport, headers, null); + } + + Rpc(NettyClientTransport transport, Metadata headers, String authorityOverride) { stream = transport.newStream( - METHOD, headers, CallOptions.DEFAULT, + METHOD, headers, authorityOverride != null + ? CallOptions.DEFAULT.withAuthority(authorityOverride) : CallOptions.DEFAULT, new ClientStreamTracer[]{ new ClientStreamTracer() {} }); stream.start(listener); stream.request(1); @@ -1317,8 +1360,15 @@ public void log(ChannelLogLevel level, String messageFormat, Object... args) {} } static class FakeClientTransportListener implements ManagedClientTransport.Listener { + private final SettableFuture connected; + + @GuardedBy("this") private boolean isConnected = false; + public FakeClientTransportListener(SettableFuture connected) { + this.connected = connected; + } + @Override public void transportShutdown(Status s) {} @@ -1327,10 +1377,14 @@ public void transportTerminated() {} @Override public void transportReady() { - isConnected = true; synchronized (this) { - notify(); + isConnected = true; } + connected.set(null); + } + + synchronized boolean isConnected() { + return isConnected; } @Override From a9a019bf44261210db952fbe135f7293e92135fd Mon Sep 17 00:00:00 2001 From: deadEternally Date: Fri, 17 Jan 2025 15:10:20 +0530 Subject: [PATCH 53/70] Remove the code handling for impossible exception. --- .../src/main/java/io/grpc/netty/ProtocolNegotiators.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 93418a4ef2f..6f1ad3f4842 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -585,8 +585,6 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws } static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { - private static final Logger logger = - Logger.getLogger(ClientTlsProtocolNegotiator.class.getName()); private static final Method checkServerTrustedMethod; static { @@ -600,8 +598,6 @@ static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { // Per-rpc authority overriding via call options will be disallowed. } catch (NoSuchMethodException e) { // Should never happen. - logger.log(Level.WARNING, "Method checkServerTrusted not found in " - + "javax.net.ssl.X509ExtendedTrustManager", e); } checkServerTrustedMethod = method; } @@ -676,10 +672,6 @@ public void setSslEngine(SSLEngine sslEngine) { private void verifyAuthorityAllowedForPeerCert(String authority) throws SSLPeerUnverifiedException, CertificateException, InvocationTargetException, IllegalAccessException { - if (checkServerTrustedMethod == null) { - throw new IllegalStateException("Method checkServerTrusted not found in " - + "javax.net.ssl.X509ExtendedTrustManager"); - } SSLEngine sslEngineWrapper = new SslEngineWrapper(sslEngine, authority); // The typecasting of Certificate to X509Certificate should work because this method will only // be called when using TLS and thus X509. From 1f56282286deccec401c214b2f1851644b312845 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Fri, 17 Jan 2025 15:23:36 +0530 Subject: [PATCH 54/70] Update comment for NoSuchMethodError. --- netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 6f1ad3f4842..2b59488ed08 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -597,7 +597,8 @@ static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { } catch (ClassNotFoundException e) { // Per-rpc authority overriding via call options will be disallowed. } catch (NoSuchMethodException e) { - // Should never happen. + // Should never happen since X509ExtendedTrustManager was introduced in Android API level 24 + // along with checkServerTrusted. } checkServerTrustedMethod = method; } From 649f53c990a2c91efe1352d3329e45117d848ae2 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Fri, 17 Jan 2025 17:08:05 +0530 Subject: [PATCH 55/70] Some changes based on similar comments in the authority check for Netty PR. --- .../java/io/grpc/ManagedChannelRegistry.java | 2 +- .../io/grpc/okhttp/OkHttpClientTransport.java | 83 ++++++++----------- .../okhttp/OkHttpClientTransportTest.java | 4 +- .../src/test/java/io/grpc/okhttp/TlsTest.java | 11 ++- 4 files changed, 43 insertions(+), 57 deletions(-) diff --git a/api/src/main/java/io/grpc/ManagedChannelRegistry.java b/api/src/main/java/io/grpc/ManagedChannelRegistry.java index fc9e693b560..aed5eca9abf 100644 --- a/api/src/main/java/io/grpc/ManagedChannelRegistry.java +++ b/api/src/main/java/io/grpc/ManagedChannelRegistry.java @@ -225,4 +225,4 @@ public ProviderNotFoundException(String msg) { super(msg); } } -} \ No newline at end of file +} diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 2cd8136c162..37bf0806fd0 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -134,10 +134,11 @@ class OkHttpClientTransport implements ConnectionClientTransport, TransportExcep private static final Logger log = Logger.getLogger(OkHttpClientTransport.class.getName()); private static final String GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK = "GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"; + static boolean enablePerRpcAuthorityCheck = + GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false); private final ChannelCredentials channelCredentials; private Socket sock; private SSLSession sslSession; - private final Logger logger = Logger.getLogger(OkHttpClientTransport.class.getName()); private static Map buildErrorCodeToStatusMap() { Map errorToStatus = new EnumMap<>(ErrorCode.class); @@ -179,9 +180,8 @@ private static Map buildErrorCodeToStatusMap() { } catch (ClassNotFoundException e) { // Per-rpc authority override via call options will be disallowed. } catch (NoSuchMethodException e) { - // Should never happen. - Logger.getLogger(OkHttpClientTransport.class.getName()).warning("Method checkServerTrusted " - + "not found in javax.net.ssl.X509ExtendedTrustManager"); + // Should never happen since X509ExtendedTrustManager was introduced in Android API level 24 + // along with checkServerTrusted. } } @@ -246,13 +246,13 @@ private static Map buildErrorCodeToStatusMap() { private final boolean useGetForSafeMethods; @GuardedBy("lock") private final TransportTracer transportTracer; - private final LinkedHashMap peerVerificationResults = + private final Map peerVerificationResults = Collections.synchronizedMap( new LinkedHashMap() { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > 100; } - }; + }); @GuardedBy("lock") private final InUseStateAggregator inUseState = @@ -453,13 +453,11 @@ public ClientStream newStream( if (hostnameVerifier != null && socket instanceof SSLSocket && !hostnameVerifier.verify(callOptions.getAuthority(), ((SSLSocket) socket).getSession())) { - if (GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false)) { + if (enablePerRpcAuthorityCheck) { return new FailingClientStream(Status.UNAVAILABLE.withDescription( String.format("HostNameVerifier verification failed for authority '%s'", callOptions.getAuthority())), tracers); } - logger.warning(String.format("HostNameVerifier verification failed for authority '%s'.", - callOptions.getAuthority())); } if (socket instanceof SSLSocket && callOptions.getAuthority() != null && channelCredentials != null && channelCredentials instanceof TlsChannelCredentials) { @@ -467,55 +465,40 @@ public ClientStream newStream( if (peerVerificationResults.containsKey(callOptions.getAuthority())) { peerVerificationStatus = peerVerificationResults.get(callOptions.getAuthority()); } else { - TrustManager x509ExtendedTrustManager = null; - boolean warningLogged = false; + TrustManager x509ExtendedTrustManager; try { x509ExtendedTrustManager = x509ExtendedTrustManagerClass != null ? getX509ExtendedTrustManager((TlsChannelCredentials) channelCredentials) : null; - } catch (GeneralSecurityException e) { - if (GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false)) { - return new FailingClientStream(Status.UNAVAILABLE.withDescription( - "Failure getting X509ExtendedTrustManager from TlsCredentials").withCause(e), - tracers); - } - logger.warning(String.format("Failure getting X509ExtendedTrustManager from " - + "TlsCredentials due to: %s", e.getMessage())); - warningLogged = true; - } - if (x509ExtendedTrustManager == null) { - if (GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false)) { - return new FailingClientStream(Status.UNAVAILABLE.withDescription( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not " - + "available"), tracers); - } - if (!warningLogged) { - logger.warning("Authority override set for rpc when X509ExtendedTrustManager is not " - + "available."); - } - } else { - try { - Certificate[] peerCertificates = sslSession.getPeerCertificates(); - X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; - for (int i = 0; i < peerCertificates.length; i++) { - x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; + if (x509ExtendedTrustManager == null) { + if (GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false)) { + return new FailingClientStream(Status.UNAVAILABLE.withDescription( + "Can't allow authority override in rpc when X509ExtendedTrustManager is not " + + "available"), tracers); } - // Should never happen - if (checkServerTrustedMethod == null) { - peerVerificationStatus = Status.UNAVAILABLE.withDescription( - "Method checkServerTrusted not found in " - + "javax.net.ssl.X509ExtendedTrustManager"); - } else { + } else { + try { + Certificate[] peerCertificates = sslSession.getPeerCertificates(); + X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; + for (int i = 0; i < peerCertificates.length; i++) { + x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; + } checkServerTrustedMethod.invoke(x509ExtendedTrustManager, x509PeerCertificates, "RSA", new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); peerVerificationStatus = Status.OK; + } catch (SSLPeerUnverifiedException | InvocationTargetException + | IllegalAccessException e) { + peerVerificationStatus = Status.UNAVAILABLE.withDescription( + String.format("Failure in verifying authority '%s' against peer during rpc", + callOptions.getAuthority())).withCause(e); } - } catch (SSLPeerUnverifiedException | InvocationTargetException - | IllegalAccessException e) { - peerVerificationStatus = Status.UNAVAILABLE.withDescription( - String.format("Failure in verifying authority '%s' against peer during rpc", - callOptions.getAuthority())).withCause(e); + peerVerificationResults.put(callOptions.getAuthority(), peerVerificationStatus); + } + } catch (GeneralSecurityException e) { + if (GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false)) { + return new FailingClientStream(Status.UNAVAILABLE.withDescription( + "Failure getting X509ExtendedTrustManager from TlsCredentials").withCause(e), + tracers); } - peerVerificationResults.put(callOptions.getAuthority(), peerVerificationStatus); } } if (peerVerificationStatus != null && !peerVerificationStatus.isOk()) { @@ -1610,7 +1593,7 @@ public void alternateService(int streamId, String origin, ByteString protocol, S /** * SSLSocket wrapper that provides a fake SSLSession for handshake session. */ - static class SslSocketWrapper extends NoopSslSocket { + static final class SslSocketWrapper extends NoopSslSocket { private final SSLSession sslSession; private final SSLSocket sslSocket; diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java index 5c46e6ebffc..9f6f48d9fdc 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java @@ -817,7 +817,7 @@ public void perRpcAuthoritySpecified_hostnameVerification_SslSocket_successCase( @Test public void perRpcAuthoritySpecified_hostnameVerification_SslSocket_failureCase() throws Exception { - System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); + OkHttpClientTransport.enablePerRpcAuthorityCheck = true; try { startTransport( DEFAULT_START_STREAM_ID, null, true, null, @@ -833,7 +833,7 @@ public void perRpcAuthoritySpecified_hostnameVerification_SslSocket_failureCase( + "cause=null}"); shutdownAndVerify(); } finally { - System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); + OkHttpClientTransport.enablePerRpcAuthorityCheck = false; } } diff --git a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java index e37f3286d95..6196a36186c 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java @@ -60,6 +60,8 @@ import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509TrustManager; import javax.security.auth.x500.X500Principal; + +import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; import org.junit.Assume; import org.junit.Before; import org.junit.Rule; @@ -69,6 +71,7 @@ /** Verify OkHttp's TLS integration. */ @RunWith(JUnit4.class) +@IgnoreJRERequirement public class TlsTest { @Rule public final GrpcCleanupRule grpcCleanupRule = new GrpcCleanupRule(); @@ -110,7 +113,7 @@ public void basicTls_succeeds() throws Exception { @Test public void perRpcAuthorityOverride_checkServerTrustedIsCalled() throws Exception { - System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); + OkHttpClientTransport.enablePerRpcAuthorityCheck = true; try { ServerCredentials serverCreds; try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); @@ -137,7 +140,7 @@ public void perRpcAuthorityOverride_checkServerTrustedIsCalled() throws Exceptio SimpleRequest.getDefaultInstance()); assertThat(fakeTrustManager.checkServerTrustedCalled).isTrue(); } finally { - System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); + OkHttpClientTransport.enablePerRpcAuthorityCheck = false; } } @@ -148,7 +151,7 @@ public void perRpcAuthorityOverride_checkServerTrustedIsCalled() throws Exceptio @Test public void perRpcAuthorityOverride_tlsCreds_noX509ExtendedTrustManager_fails() throws Exception { - System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); + OkHttpClientTransport.enablePerRpcAuthorityCheck = true; try { ServerCredentials serverCreds; try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); @@ -180,7 +183,7 @@ public void perRpcAuthorityOverride_tlsCreds_noX509ExtendedTrustManager_fails() + "available"); } } finally { - System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); + OkHttpClientTransport.enablePerRpcAuthorityCheck = false; } } From 73bb42fd0adfbde2e15e5ca0d54a93538ad4435c Mon Sep 17 00:00:00 2001 From: deadEternally Date: Fri, 17 Jan 2025 17:20:49 +0530 Subject: [PATCH 56/70] Animal sniffer suppress --- okhttp/src/test/java/io/grpc/okhttp/TlsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java index 6196a36186c..79c7d281baa 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java @@ -71,7 +71,6 @@ /** Verify OkHttp's TLS integration. */ @RunWith(JUnit4.class) -@IgnoreJRERequirement public class TlsTest { @Rule public final GrpcCleanupRule grpcCleanupRule = new GrpcCleanupRule(); @@ -432,6 +431,7 @@ public X509Certificate[] getAcceptedIssuers() { /** Used to capture the fact that checkServerTrusted has been called for the per-rpc authority * verification. */ + @IgnoreJRERequirement private static class FakeX509ExtendedTrustManager extends X509ExtendedTrustManager { private final X509ExtendedTrustManager delegate; private boolean checkServerTrustedCalled; From 2af1cca55594b4607146504f84338eb1dea3fe52 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Wed, 22 Jan 2025 15:25:22 +0530 Subject: [PATCH 57/70] Ignore animal sniffer errors via annotation in tests. --- okhttp/src/test/java/io/grpc/okhttp/TlsTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java index 79c7d281baa..64512f07cde 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java @@ -71,6 +71,7 @@ /** Verify OkHttp's TLS integration. */ @RunWith(JUnit4.class) +@IgnoreJRERequirement public class TlsTest { @Rule public final GrpcCleanupRule grpcCleanupRule = new GrpcCleanupRule(); From 15c81613eabf64aa781e43011f694fd5cc5d8d43 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Fri, 24 Jan 2025 17:14:54 +0530 Subject: [PATCH 58/70] In-progress changes to move authority verification to the ChannelHandler instead of the Protocol negotiator. --- .../io/grpc/internal/AuthorityVerifier.java | 7 ++ .../java/io/grpc/internal/GrpcAttributes.java | 2 + examples/build.gradle | 2 +- examples/example-tls/build.gradle | 2 +- .../netty/InternalProtocolNegotiators.java | 2 +- .../io/grpc/netty/ProtocolNegotiators.java | 75 +++--------------- .../io/grpc/netty/X509AuthorityVerifier.java | 77 +++++++++++++++++++ .../grpc/netty/ProtocolNegotiatorsTest.java | 8 +- 8 files changed, 105 insertions(+), 70 deletions(-) create mode 100644 core/src/main/java/io/grpc/internal/AuthorityVerifier.java create mode 100644 netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java diff --git a/core/src/main/java/io/grpc/internal/AuthorityVerifier.java b/core/src/main/java/io/grpc/internal/AuthorityVerifier.java new file mode 100644 index 00000000000..b24d81e81dc --- /dev/null +++ b/core/src/main/java/io/grpc/internal/AuthorityVerifier.java @@ -0,0 +1,7 @@ +package io.grpc.internal; + +import io.grpc.Status; + +public interface AuthorityVerifier { + Status verifyAuthority(String authority); +} diff --git a/core/src/main/java/io/grpc/internal/GrpcAttributes.java b/core/src/main/java/io/grpc/internal/GrpcAttributes.java index da43ae14800..ad808bef6da 100644 --- a/core/src/main/java/io/grpc/internal/GrpcAttributes.java +++ b/core/src/main/java/io/grpc/internal/GrpcAttributes.java @@ -42,5 +42,7 @@ public final class GrpcAttributes { public static final Attributes.Key ATTR_CLIENT_EAG_ATTRS = Attributes.Key.create("io.grpc.internal.GrpcAttributes.clientEagAttrs"); + public static final Attributes.Key ATTR_AUTHORITY_VERIFIER = + Attributes.Key.create("io.grpc.internal.GrpcAttributes.authorityVerifier"); private GrpcAttributes() {} } diff --git a/examples/build.gradle b/examples/build.gradle index d4991f02f43..761fd9d9e6a 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -45,7 +45,7 @@ dependencies { protobuf { protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" } plugins { - grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } + grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.70.0" } } generateProtoTasks { all()*.plugins { grpc {} } diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 87e37c5a3b1..8980bc04e1e 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -34,7 +34,7 @@ dependencies { protobuf { protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" } plugins { - grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } + grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.70.0" } } generateProtoTasks { all()*.plugins { grpc {} } diff --git a/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java index fb66571df73..039ea6c4f24 100644 --- a/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java @@ -170,7 +170,7 @@ public static ChannelHandler clientTlsHandler( ChannelHandler next, SslContext sslContext, String authority, ChannelLogger negotiationLogger) { return new ClientTlsHandler(next, sslContext, authority, null, negotiationLogger, - Optional.absent(), null); + Optional.absent(), null, null); } public static class ProtocolNegotiationHandler diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 2b59488ed08..2e56e6784b2 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -585,25 +585,6 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws } static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { - private static final Method checkServerTrustedMethod; - - static { - Method method = null; - try { - Class x509ExtendedTrustManagerClass = - Class.forName("javax.net.ssl.X509ExtendedTrustManager"); - method = x509ExtendedTrustManagerClass.getMethod("checkServerTrusted", - X509Certificate[].class, String.class, SSLEngine.class); - } catch (ClassNotFoundException e) { - // Per-rpc authority overriding via call options will be disallowed. - } catch (NoSuchMethodException e) { - // Should never happen since X509ExtendedTrustManager was introduced in Android API level 24 - // along with checkServerTrusted. - } - checkServerTrustedMethod = method; - } - - private SSLEngine sslEngine; public ClientTlsProtocolNegotiator(SslContext sslContext, ObjectPool executorPool, Optional handshakeCompleteRunnable, @@ -633,7 +614,8 @@ public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) { ChannelHandler gnh = new GrpcNegotiationHandler(grpcHandler); ChannelLogger negotiationLogger = grpcHandler.getNegotiationLogger(); ChannelHandler cth = new ClientTlsHandler(gnh, sslContext, grpcHandler.getAuthority(), - this.executor, negotiationLogger, handshakeCompleteRunnable, this); + this.executor, negotiationLogger, handshakeCompleteRunnable, this, + x509ExtendedTrustManager); return new WaitUntilActiveHandler(cth, negotiationLogger); } @@ -644,47 +626,6 @@ public void close() { } } - @Override - public Status verifyAuthority(@Nonnull String authority) { - // sslEngine won't be set when creating ClientTlsHandler from InternalProtocolNegotiators - // for example. - if (sslEngine == null || x509ExtendedTrustManager == null) { - return Status.FAILED_PRECONDITION.withDescription( - "Can't allow authority override in rpc when SslEngine or X509ExtendedTrustManager" - + " is not available"); - } - Status peerVerificationStatus; - try { - verifyAuthorityAllowedForPeerCert(authority); - peerVerificationStatus = Status.OK; - } catch (SSLPeerUnverifiedException | CertificateException | InvocationTargetException - | IllegalAccessException | IllegalStateException e) { - peerVerificationStatus = Status.UNAVAILABLE.withDescription( - String.format("Peer hostname verification during rpc failed for authority '%s'", - authority)).withCause(e); - } - return peerVerificationStatus; - } - - public void setSslEngine(SSLEngine sslEngine) { - this.sslEngine = sslEngine; - } - - private void verifyAuthorityAllowedForPeerCert(String authority) - throws SSLPeerUnverifiedException, CertificateException, InvocationTargetException, - IllegalAccessException { - SSLEngine sslEngineWrapper = new SslEngineWrapper(sslEngine, authority); - // The typecasting of Certificate to X509Certificate should work because this method will only - // be called when using TLS and thus X509. - Certificate[] peerCertificates = sslEngine.getSession().getPeerCertificates(); - X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; - for (int i = 0; i < peerCertificates.length; i++) { - x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; - } - checkServerTrustedMethod.invoke( - x509ExtendedTrustManager, x509PeerCertificates, "RSA", sslEngineWrapper); - } - @VisibleForTesting boolean hasX509ExtendedTrustManager() { return x509ExtendedTrustManager != null; @@ -699,11 +640,13 @@ static final class ClientTlsHandler extends ProtocolNegotiationHandler { private final ClientTlsProtocolNegotiator clientTlsProtocolNegotiator; private Executor executor; private final Optional handshakeCompleteRunnable; + private final X509TrustManager x509ExtendedTrustManager; ClientTlsHandler(ChannelHandler next, SslContext sslContext, String authority, Executor executor, ChannelLogger negotiationLogger, Optional handshakeCompleteRunnable, - ClientTlsProtocolNegotiator clientTlsProtocolNegotiator) { + ClientTlsProtocolNegotiator clientTlsProtocolNegotiator, + X509TrustManager x509ExtendedTrustManager) { super(next, negotiationLogger); this.sslContext = checkNotNull(sslContext, "sslContext"); HostPort hostPort = parseAuthority(authority); @@ -712,6 +655,7 @@ static final class ClientTlsHandler extends ProtocolNegotiationHandler { this.executor = executor; this.handshakeCompleteRunnable = handshakeCompleteRunnable; this.clientTlsProtocolNegotiator = clientTlsProtocolNegotiator; + this.x509ExtendedTrustManager = x509ExtendedTrustManager; } @Override @@ -724,7 +668,12 @@ protected void handlerAdded0(ChannelHandlerContext ctx) { ctx.pipeline().addBefore(ctx.name(), /* name= */ null, this.executor != null ? new SslHandler(sslEngine, false, this.executor) : new SslHandler(sslEngine, false)); - clientTlsProtocolNegotiator.setSslEngine(sslEngine); + ProtocolNegotiationEvent existingPne = getProtocolNegotiationEvent(); + Attributes attrs = existingPne.getAttributes().toBuilder() + .set(GrpcAttributes.ATTR_AUTHORITY_VERIFIER, new X509AuthorityVerifier( + sslEngine, x509ExtendedTrustManager)) + .build(); + replaceProtocolNegotiationEvent(existingPne.withAttributes(attrs)); } @Override diff --git a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java new file mode 100644 index 00000000000..169da4b7bf7 --- /dev/null +++ b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java @@ -0,0 +1,77 @@ +package io.grpc.netty; + +import io.grpc.Status; +import io.grpc.internal.AuthorityVerifier; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import javax.annotation.Nonnull; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.X509TrustManager; + +public class X509AuthorityVerifier implements AuthorityVerifier { + private final SSLEngine sslEngine; + private final X509TrustManager x509ExtendedTrustManager; + + private static final Method checkServerTrustedMethod; + + static { + Method method = null; + try { + Class x509ExtendedTrustManagerClass = + Class.forName("javax.net.ssl.X509ExtendedTrustManager"); + method = x509ExtendedTrustManagerClass.getMethod("checkServerTrusted", + X509Certificate[].class, String.class, SSLEngine.class); + } catch (ClassNotFoundException e) { + // Per-rpc authority overriding via call options will be disallowed. + } catch (NoSuchMethodException e) { + // Should never happen since X509ExtendedTrustManager was introduced in Android API level 24 + // along with checkServerTrusted. + } + checkServerTrustedMethod = method; + } + + public X509AuthorityVerifier(SSLEngine sslEngine, X509TrustManager x509ExtendedTrustManager) { + this.sslEngine = sslEngine; + this.x509ExtendedTrustManager = x509ExtendedTrustManager; + } + + public Status verifyAuthority(@Nonnull String authority) { + // sslEngine won't be set when creating ClientTlsHandler from InternalProtocolNegotiators + // for example. + if (sslEngine == null || x509ExtendedTrustManager == null) { + return Status.FAILED_PRECONDITION.withDescription( + "Can't allow authority override in rpc when SslEngine or X509ExtendedTrustManager" + + " is not available"); + } + Status peerVerificationStatus; + try { + verifyAuthorityAllowedForPeerCert(authority); + peerVerificationStatus = Status.OK; + } catch (SSLPeerUnverifiedException | CertificateException | InvocationTargetException + | IllegalAccessException | IllegalStateException e) { + peerVerificationStatus = Status.UNAVAILABLE.withDescription( + String.format("Peer hostname verification during rpc failed for authority '%s'", + authority)).withCause(e); + } + return peerVerificationStatus; + } + + private void verifyAuthorityAllowedForPeerCert(String authority) + throws SSLPeerUnverifiedException, CertificateException, InvocationTargetException, + IllegalAccessException { + SSLEngine sslEngineWrapper = new ProtocolNegotiators.SslEngineWrapper(sslEngine, authority); + // The typecasting of Certificate to X509Certificate should work because this method will only + // be called when using TLS and thus X509. + Certificate[] peerCertificates = sslEngine.getSession().getPeerCertificates(); + X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; + for (int i = 0; i < peerCertificates.length; i++) { + x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; + } + checkServerTrustedMethod.invoke( + x509ExtendedTrustManager, x509PeerCertificates, "RSA", sslEngineWrapper); + } +} diff --git a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java index 1e5a454480b..4829bcc7419 100644 --- a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java +++ b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java @@ -921,7 +921,7 @@ public String applicationProtocol() { ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, "authority", elg, noopLogger, Optional.absent(), - getClientTlsProtocolNegotiator()); + getClientTlsProtocolNegotiator(), null); pipeline.addLast(handler); pipeline.replace(SslHandler.class, null, goodSslHandler); pipeline.fireUserEventTriggered(ProtocolNegotiationEvent.DEFAULT); @@ -960,7 +960,7 @@ public String applicationProtocol() { ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, "authority", elg, noopLogger, Optional.absent(), - getClientTlsProtocolNegotiator()); + getClientTlsProtocolNegotiator(), null); pipeline.addLast(handler); pipeline.replace(SslHandler.class, null, goodSslHandler); pipeline.fireUserEventTriggered(ProtocolNegotiationEvent.DEFAULT); @@ -985,7 +985,7 @@ public String applicationProtocol() { ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, "authority", elg, noopLogger, Optional.absent(), - getClientTlsProtocolNegotiator()); + getClientTlsProtocolNegotiator(), null); pipeline.addLast(handler); final AtomicReference error = new AtomicReference<>(); @@ -1014,7 +1014,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { public void clientTlsHandler_closeDuringNegotiation() throws Exception { ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext, "authority", null, noopLogger, Optional.absent(), - getClientTlsProtocolNegotiator()); + getClientTlsProtocolNegotiator(), null); pipeline.addLast(new WriteBufferingAndExceptionHandler(handler)); ChannelFuture pendingWrite = channel.writeAndFlush(NettyClientHandler.NOOP_MESSAGE); From 7941abcb3447a05c6282696fa831701bb879c628 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Fri, 24 Jan 2025 13:55:29 +0000 Subject: [PATCH 59/70] Save sslEngine and use it later after the handshake is complete, to set the attribute for the hostname verifier. --- examples/example-tls/build.gradle | 4 +--- .../main/java/io/grpc/netty/ProtocolNegotiators.java | 11 ++++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 8980bc04e1e..4af0e8565e5 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -71,8 +71,6 @@ application { applicationDistribution.into('bin') { from(helloWorldTlsServer) from(helloWorldTlsClient) - filePermissions { - unix(0755) - } + fileMode = 0755 } } diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 2e56e6784b2..82f06dd8fa9 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -641,6 +641,7 @@ static final class ClientTlsHandler extends ProtocolNegotiationHandler { private Executor executor; private final Optional handshakeCompleteRunnable; private final X509TrustManager x509ExtendedTrustManager; + private SSLEngine sslEngine; ClientTlsHandler(ChannelHandler next, SslContext sslContext, String authority, Executor executor, ChannelLogger negotiationLogger, @@ -661,19 +662,13 @@ static final class ClientTlsHandler extends ProtocolNegotiationHandler { @Override @IgnoreJRERequirement protected void handlerAdded0(ChannelHandlerContext ctx) { - SSLEngine sslEngine = sslContext.newEngine(ctx.alloc(), host, port); + sslEngine = sslContext.newEngine(ctx.alloc(), host, port); SSLParameters sslParams = sslEngine.getSSLParameters(); sslParams.setEndpointIdentificationAlgorithm("HTTPS"); sslEngine.setSSLParameters(sslParams); ctx.pipeline().addBefore(ctx.name(), /* name= */ null, this.executor != null ? new SslHandler(sslEngine, false, this.executor) : new SslHandler(sslEngine, false)); - ProtocolNegotiationEvent existingPne = getProtocolNegotiationEvent(); - Attributes attrs = existingPne.getAttributes().toBuilder() - .set(GrpcAttributes.ATTR_AUTHORITY_VERIFIER, new X509AuthorityVerifier( - sslEngine, x509ExtendedTrustManager)) - .build(); - replaceProtocolNegotiationEvent(existingPne.withAttributes(attrs)); } @Override @@ -724,6 +719,8 @@ private void propagateTlsComplete(ChannelHandlerContext ctx, SSLSession session) Attributes attrs = existingPne.getAttributes().toBuilder() .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY) .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, session) + .set(GrpcAttributes.ATTR_AUTHORITY_VERIFIER, new X509AuthorityVerifier( + sslEngine, x509ExtendedTrustManager)) .build(); replaceProtocolNegotiationEvent(existingPne.withAttributes(attrs).withSecurity(security)); if (handshakeCompleteRunnable.isPresent()) { From 7fcd98dbce6d64469c7e033260f0beadc701e5de Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 29 Jan 2025 10:18:06 +0000 Subject: [PATCH 60/70] temp testing changes --- .../examples/helloworldtls/HelloWorldClientTls.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java index 4ff7e23a299..4857922107f 100644 --- a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java +++ b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java @@ -16,6 +16,9 @@ package io.grpc.examples.helloworldtls; +import static io.grpc.examples.helloworld.GreeterGrpc.getSayHelloMethod; + +import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.Grpc; import io.grpc.ManagedChannel; @@ -25,6 +28,7 @@ import io.grpc.examples.helloworld.HelloReply; import io.grpc.examples.helloworld.HelloRequest; import java.io.File; +import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -52,7 +56,11 @@ public void greet(String name) { HelloRequest request = HelloRequest.newBuilder().setName(name).build(); HelloReply response; try { - response = blockingStub.sayHello(request); + CallOptions callOptions = blockingStub.getCallOptions() + .withAuthority("moo.test.goog.fr"); + response = io.grpc.stub.ClientCalls.blockingUnaryCall( + blockingStub.getChannel(), getSayHelloMethod(), + callOptions, request); } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); return; From 4c50e71a5e6e1979367c5846d17b0d2233761255 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Thu, 30 Jan 2025 16:22:24 +0000 Subject: [PATCH 61/70] Changes. --- .../helloworldtls/HelloWorldClientTls.java | 6 +++- .../grpc/netty/GrpcHttp2OutboundHeaders.java | 9 +++++ .../io/grpc/netty/NettyClientHandler.java | 33 +++++++++++++++++++ .../io/grpc/netty/NettyClientTransport.java | 32 ------------------ .../grpc/netty/NettyClientTransportTest.java | 16 ++++----- 5 files changed, 55 insertions(+), 41 deletions(-) diff --git a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java index 4ff7e23a299..195f8d3d4ac 100644 --- a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java +++ b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java @@ -16,6 +16,8 @@ package io.grpc.examples.helloworldtls; +import static io.grpc.examples.helloworld.GreeterGrpc.getSayHelloMethod; + import io.grpc.Channel; import io.grpc.Grpc; import io.grpc.ManagedChannel; @@ -48,11 +50,13 @@ public HelloWorldClientTls(Channel channel) { * Say hello to server. */ public void greet(String name) { + System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); logger.info("Will try to greet " + name + " ..."); HelloRequest request = HelloRequest.newBuilder().setName(name).build(); HelloReply response; try { - response = blockingStub.sayHello(request); + response = io.grpc.stub.ClientCalls.blockingUnaryCall( + blockingStub.getChannel(), getSayHelloMethod(), blockingStub.getCallOptions().withAuthority("foo.goog.test.in"), request); } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); return; diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java index 0489e135813..6ba66fe4ff4 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java @@ -46,6 +46,15 @@ static GrpcHttp2OutboundHeaders clientRequestHeaders(byte[][] serializedMetadata return new GrpcHttp2OutboundHeaders(preHeaders, serializedMetadata); } + String getAuthority() { + for (int i = 0; i < preHeaders.length / 2; i++) { + if (preHeaders[i] == Http2Headers.PseudoHeaderName.AUTHORITY.value()) { + return preHeaders[i + 1].toString(); + } + } + return null; + } + static GrpcHttp2OutboundHeaders serverResponseHeaders(byte[][] serializedMetadata) { AsciiString[] preHeaders = new AsciiString[] { Http2Headers.PseudoHeaderName.STATUS.value(), Utils.STATUS_OK, diff --git a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java index 194decb1120..295cd3cb829 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java @@ -83,6 +83,8 @@ import io.perfmark.Tag; import io.perfmark.TaskCloseable; import java.nio.channels.ClosedChannelException; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; @@ -94,6 +96,8 @@ */ class NettyClientHandler extends AbstractNettyHandler { private static final Logger logger = Logger.getLogger(NettyClientHandler.class.getName()); + static boolean enablePerRpcAuthorityCheck = + GrpcUtil.getFlag("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", false); /** * A message that simply passes through the channel without any real processing. It is useful to @@ -128,6 +132,13 @@ protected void handleNotInUse() { lifecycleManager.notifyInUse(false); } }; + private final Map peerVerificationResults = + new LinkedHashMap() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > 100; + } + }; private WriteQueue clientWriteQueue; private Http2Ping ping; @@ -591,6 +602,28 @@ private void createStream(CreateStreamCommand command, ChannelPromise promise) return; } + String authority = ((GrpcHttp2OutboundHeaders) command.headers()).getAuthority(); + if (authority != null) { + Status authorityVerificationStatus = peerVerificationResults.get(authority); + if (authorityVerificationStatus == null) { + authorityVerificationStatus = attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) + .verifyAuthority(((GrpcHttp2OutboundHeaders) command.headers()).getAuthority()); + peerVerificationResults.put(authority, authorityVerificationStatus); + } + if (!authorityVerificationStatus.isOk()) { + logger.log(Level.WARNING, String.format("%s.%s", + authorityVerificationStatus.getDescription(), enablePerRpcAuthorityCheck + ? "" : "This will be an error in the future."), + authorityVerificationStatus.getCause()); + if (enablePerRpcAuthorityCheck) { + command.stream().setNonExistent(); + command.stream().transportReportStatus( + authorityVerificationStatus, RpcProgress.DROPPED, true, new Metadata()); + promise.setFailure(authorityVerificationStatus.getCause()); + return; + } + } + } // Get the stream ID for the new stream. int streamId; try { diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index 36993b6df5a..83b6c46caf9 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -60,12 +60,9 @@ import io.netty.util.concurrent.GenericFutureListener; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; -import java.util.Collections; -import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -110,17 +107,7 @@ class NettyClientTransport implements ConnectionClientTransport { private final boolean useGetForSafeMethods; private final Ticker ticker; private final Logger logger = Logger.getLogger(NettyClientTransport.class.getName()); - private final Map peerVerificationResults = Collections.synchronizedMap( - new LinkedHashMap() { - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > 100; - } - }); - @VisibleForTesting - static boolean enablePerRpcAuthorityCheck = - GrpcUtil.getFlag("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", false); NettyClientTransport( SocketAddress address, @@ -210,25 +197,6 @@ public ClientStream newStream( if (channel == null) { return new FailingClientStream(statusExplainingWhyTheChannelIsNull, tracers); } - if (callOptions.getAuthority() != null) { - Status verificationStatus = peerVerificationResults.get(callOptions.getAuthority()); - if (verificationStatus == null) { - verificationStatus = negotiator.verifyAuthority(callOptions.getAuthority()); - peerVerificationResults.put(callOptions.getAuthority(), verificationStatus); - if (!verificationStatus.isOk()) { - logger.log(Level.WARNING, String.format("Peer hostname verification during rpc failed " - + "for authority '%s' for method '%s' with the error \"%s\". This will " - + "be an error in the future.", callOptions.getAuthority(), - method.getFullMethodName(), verificationStatus.getDescription()), - verificationStatus.getCause()); - } - } - if (!verificationStatus.isOk()) { - if (enablePerRpcAuthorityCheck) { - return new FailingClientStream(verificationStatus, tracers); - } - } - } StatsTraceContext statsTraceCtx = StatsTraceContext.newClientContext(tracers, getAttributes(), headers); return new NettyClientStream( diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 7b37a294d95..f2c238dacba 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -867,7 +867,7 @@ public void tlsNegotiationServerExecutorShouldSucceed() throws Exception { public void authorityOverrideInCallOptions_noX509ExtendedTrustManager_newStreamCreationFails() throws IOException, InterruptedException, GeneralSecurityException, ExecutionException, TimeoutException { - NettyClientTransport.enablePerRpcAuthorityCheck = true; + NettyClientHandler.enablePerRpcAuthorityCheck = true; try { startServer(); InputStream caCert = TlsTesting.loadCert("ca.pem"); @@ -895,7 +895,7 @@ public void authorityOverrideInCallOptions_noX509ExtendedTrustManager_newStreamC assertThat(status.getCode()).isEqualTo(Code.FAILED_PRECONDITION); } } finally { - NettyClientTransport.enablePerRpcAuthorityCheck = false; + NettyClientHandler.enablePerRpcAuthorityCheck = false; } } @@ -903,7 +903,7 @@ public void authorityOverrideInCallOptions_noX509ExtendedTrustManager_newStreamC public void authorityOverrideInCallOptions_doesntMatchServerPeerHost_newStreamCreationFails() throws IOException, InterruptedException, GeneralSecurityException, ExecutionException, TimeoutException { - NettyClientTransport.enablePerRpcAuthorityCheck = true; + NettyClientHandler.enablePerRpcAuthorityCheck = true; try { startServer(); NettyClientTransport transport = newTransport(newNegotiator()); @@ -930,7 +930,7 @@ public void authorityOverrideInCallOptions_doesntMatchServerPeerHost_newStreamCr "No subject alternative DNS name matching foo.test.google.in found."); } } finally { - NettyClientTransport.enablePerRpcAuthorityCheck = false; + NettyClientHandler.enablePerRpcAuthorityCheck = false; } } @@ -938,7 +938,7 @@ public void authorityOverrideInCallOptions_doesntMatchServerPeerHost_newStreamCr public void authorityOverrideInCallOptions_matchesServerPeerHost_newStreamCreationSucceeds() throws IOException, InterruptedException, GeneralSecurityException, ExecutionException, TimeoutException { - NettyClientTransport.enablePerRpcAuthorityCheck = true; + NettyClientHandler.enablePerRpcAuthorityCheck = true; try { startServer(); NettyClientTransport transport = newTransport(newNegotiator()); @@ -951,7 +951,7 @@ public void authorityOverrideInCallOptions_matchesServerPeerHost_newStreamCreati new Rpc(transport, new Metadata(), "foo.test.google.fr").waitForResponse(); } finally { - NettyClientTransport.enablePerRpcAuthorityCheck = false;; + NettyClientHandler.enablePerRpcAuthorityCheck = false;; } } @@ -959,7 +959,7 @@ public void authorityOverrideInCallOptions_matchesServerPeerHost_newStreamCreati public void authorityOverrideInCallOptions_lruCache() throws IOException, InterruptedException, GeneralSecurityException, ExecutionException, TimeoutException { - NettyClientTransport.enablePerRpcAuthorityCheck = true; + NettyClientHandler.enablePerRpcAuthorityCheck = true; try { startServer(); ProtocolNegotiator mockNegotiator = @@ -997,7 +997,7 @@ public void authorityOverrideInCallOptions_lruCache() assertThat(authorityValues.get(100)).isEqualTo("foo-100.test.google.fr"); assertThat(authorityValues.get(101)).isEqualTo("foo-0.test.google.fr"); } finally { - NettyClientTransport.enablePerRpcAuthorityCheck = false;; + NettyClientHandler.enablePerRpcAuthorityCheck = false;; } } From 02a104195dc3f352e761f469b119685df0b46608 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Thu, 30 Jan 2025 17:01:22 +0000 Subject: [PATCH 62/70] Revert "Merge remote-tracking branch 'origin/authorityverifyokhttp' into authoritychecktls" This reverts commit 8576a4efdbe2aa2ac080c7d3e30b8327e9183376, reversing changes made to da19b2886ac3b793b8deb7d0f9059b99f4292b73. --- .../io/grpc/internal/CertificateUtils.java | 20 +- .../java/io/grpc/okhttp/NoopSslSocket.java | 117 ------ .../io/grpc/okhttp/OkHttpClientTransport.java | 217 +--------- .../io/grpc/okhttp/OkHttpServerBuilder.java | 3 +- .../okhttp/OkHttpClientTransportTest.java | 384 ++++-------------- .../src/test/java/io/grpc/okhttp/TlsTest.java | 218 ---------- 6 files changed, 102 insertions(+), 857 deletions(-) delete mode 100644 okhttp/src/main/java/io/grpc/okhttp/NoopSslSocket.java diff --git a/core/src/main/java/io/grpc/internal/CertificateUtils.java b/core/src/main/java/io/grpc/internal/CertificateUtils.java index 11abe7610ec..7efd16eaf27 100644 --- a/core/src/main/java/io/grpc/internal/CertificateUtils.java +++ b/core/src/main/java/io/grpc/internal/CertificateUtils.java @@ -16,7 +16,6 @@ package io.grpc.internal; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; @@ -37,21 +36,8 @@ public class CertificateUtils { /** * Creates X509TrustManagers using the provided CA certs. */ - public static TrustManager[] createTrustManager(byte[] rootCerts) - throws GeneralSecurityException { - InputStream rootCertsStream = new ByteArrayInputStream(rootCerts); - try { - return io.grpc.internal.CertificateUtils.createTrustManager(rootCertsStream); - } finally { - GrpcUtil.closeQuietly(rootCertsStream); - } - } - - /** - * Creates X509TrustManagers using the provided input stream of CA certs. - */ public static TrustManager[] createTrustManager(InputStream rootCerts) - throws GeneralSecurityException { + throws GeneralSecurityException { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); try { ks.load(null, null); @@ -66,13 +52,13 @@ public static TrustManager[] createTrustManager(InputStream rootCerts) } TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(ks); return trustManagerFactory.getTrustManagers(); } private static X509Certificate[] getX509Certificates(InputStream inputStream) - throws CertificateException { + throws CertificateException { CertificateFactory factory = CertificateFactory.getInstance("X.509"); Collection certs = factory.generateCertificates(inputStream); return certs.toArray(new X509Certificate[0]); diff --git a/okhttp/src/main/java/io/grpc/okhttp/NoopSslSocket.java b/okhttp/src/main/java/io/grpc/okhttp/NoopSslSocket.java deleted file mode 100644 index 6e6a6f12a39..00000000000 --- a/okhttp/src/main/java/io/grpc/okhttp/NoopSslSocket.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2024 The gRPC Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.grpc.okhttp; - -import java.io.IOException; -import javax.net.ssl.HandshakeCompletedListener; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; - -/** A no-op ssl socket, to facilitate overriding only the required methods in specific - * implementations. - */ -class NoopSslSocket extends SSLSocket { - @Override - public String[] getSupportedCipherSuites() { - return new String[0]; - } - - @Override - public String[] getEnabledCipherSuites() { - return new String[0]; - } - - @Override - public void setEnabledCipherSuites(String[] suites) { - - } - - @Override - public String[] getSupportedProtocols() { - return new String[0]; - } - - @Override - public String[] getEnabledProtocols() { - return new String[0]; - } - - @Override - public void setEnabledProtocols(String[] protocols) { - - } - - @Override - public SSLSession getSession() { - return null; - } - - @Override - public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { - - } - - @Override - public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { - - } - - @Override - public void startHandshake() throws IOException { - - } - - @Override - public void setUseClientMode(boolean mode) { - - } - - @Override - public boolean getUseClientMode() { - return false; - } - - @Override - public void setNeedClientAuth(boolean need) { - - } - - @Override - public boolean getNeedClientAuth() { - return false; - } - - @Override - public void setWantClientAuth(boolean want) { - - } - - @Override - public boolean getWantClientAuth() { - return false; - } - - @Override - public void setEnableSessionCreation(boolean flag) { - - } - - @Override - public boolean getEnableSessionCreation() { - return false; - } -} diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 37bf0806fd0..055d6e08161 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -30,7 +30,6 @@ import com.google.errorprone.annotations.concurrent.GuardedBy; import io.grpc.Attributes; import io.grpc.CallOptions; -import io.grpc.ChannelCredentials; import io.grpc.ClientStreamTracer; import io.grpc.Grpc; import io.grpc.HttpConnectProxiedSocketAddress; @@ -44,24 +43,18 @@ import io.grpc.Status; import io.grpc.Status.Code; import io.grpc.StatusException; -import io.grpc.TlsChannelCredentials; -import io.grpc.internal.CertificateUtils; -import io.grpc.internal.ClientStream; import io.grpc.internal.ClientStreamListener.RpcProgress; import io.grpc.internal.ConnectionClientTransport; -import io.grpc.internal.FailingClientStream; import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; import io.grpc.internal.Http2Ping; import io.grpc.internal.InUseStateAggregator; import io.grpc.internal.KeepAliveManager; import io.grpc.internal.KeepAliveManager.ClientKeepAlivePinger; -import io.grpc.internal.NoopSslSession; import io.grpc.internal.SerializingExecutor; import io.grpc.internal.StatsTraceContext; import io.grpc.internal.TransportTracer; import io.grpc.okhttp.ExceptionHandlingFrameWriter.TransportExceptionHandler; -import io.grpc.okhttp.OkHttpChannelBuilder.OkHttpTransportFactory; import io.grpc.okhttp.internal.ConnectionSpec; import io.grpc.okhttp.internal.Credentials; import io.grpc.okhttp.internal.StatusLine; @@ -78,21 +71,14 @@ import io.perfmark.PerfMark; import java.io.EOFException; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.Socket; import java.net.URI; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; import java.util.Collections; import java.util.Deque; import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -110,13 +96,9 @@ import javax.annotation.Nullable; import javax.net.SocketFactory; import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLParameters; -import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; import okio.Buffer; import okio.BufferedSink; import okio.BufferedSource; @@ -132,13 +114,6 @@ class OkHttpClientTransport implements ConnectionClientTransport, TransportExcep OutboundFlowController.Transport { private static final Map ERROR_CODE_TO_STATUS = buildErrorCodeToStatusMap(); private static final Logger log = Logger.getLogger(OkHttpClientTransport.class.getName()); - private static final String GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK = - "GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"; - static boolean enablePerRpcAuthorityCheck = - GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false); - private final ChannelCredentials channelCredentials; - private Socket sock; - private SSLSession sslSession; private static Map buildErrorCodeToStatusMap() { Map errorToStatus = new EnumMap<>(ErrorCode.class); @@ -169,22 +144,6 @@ private static Map buildErrorCodeToStatusMap() { return Collections.unmodifiableMap(errorToStatus); } - private static Class x509ExtendedTrustManagerClass; - private static Method checkServerTrustedMethod; - - static { - try { - x509ExtendedTrustManagerClass = Class.forName("javax.net.ssl.X509ExtendedTrustManager"); - checkServerTrustedMethod = x509ExtendedTrustManagerClass.getMethod("checkServerTrusted", - X509Certificate[].class, String.class, Socket.class); - } catch (ClassNotFoundException e) { - // Per-rpc authority override via call options will be disallowed. - } catch (NoSuchMethodException e) { - // Should never happen since X509ExtendedTrustManager was introduced in Android API level 24 - // along with checkServerTrusted. - } - } - private final InetSocketAddress address; private final String defaultAuthority; private final String userAgent; @@ -246,14 +205,6 @@ private static Map buildErrorCodeToStatusMap() { private final boolean useGetForSafeMethods; @GuardedBy("lock") private final TransportTracer transportTracer; - private final Map peerVerificationResults = Collections.synchronizedMap( - new LinkedHashMap() { - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > 100; - } - }); - @GuardedBy("lock") private final InUseStateAggregator inUseState = new InUseStateAggregator() { @@ -282,14 +233,13 @@ protected void handleNotInUse() { SettableFuture connectedFuture; public OkHttpClientTransport( - OkHttpTransportFactory transportFactory, - InetSocketAddress address, - String authority, - @Nullable String userAgent, - Attributes eagAttrs, - @Nullable HttpConnectProxiedSocketAddress proxiedAddr, - Runnable tooManyPingsRunnable, - ChannelCredentials channelCredentials) { + OkHttpChannelBuilder.OkHttpTransportFactory transportFactory, + InetSocketAddress address, + String authority, + @Nullable String userAgent, + Attributes eagAttrs, + @Nullable HttpConnectProxiedSocketAddress proxiedAddr, + Runnable tooManyPingsRunnable) { this( transportFactory, address, @@ -299,21 +249,19 @@ public OkHttpClientTransport( GrpcUtil.STOPWATCH_SUPPLIER, new Http2(), proxiedAddr, - tooManyPingsRunnable, - channelCredentials); + tooManyPingsRunnable); } private OkHttpClientTransport( - OkHttpTransportFactory transportFactory, - InetSocketAddress address, - String authority, - @Nullable String userAgent, - Attributes eagAttrs, - Supplier stopwatchFactory, - Variant variant, - @Nullable HttpConnectProxiedSocketAddress proxiedAddr, - Runnable tooManyPingsRunnable, - ChannelCredentials channelCredentials) { + OkHttpChannelBuilder.OkHttpTransportFactory transportFactory, + InetSocketAddress address, + String authority, + @Nullable String userAgent, + Attributes eagAttrs, + Supplier stopwatchFactory, + Variant variant, + @Nullable HttpConnectProxiedSocketAddress proxiedAddr, + Runnable tooManyPingsRunnable) { this.address = Preconditions.checkNotNull(address, "address"); this.defaultAuthority = authority; this.maxMessageSize = transportFactory.maxMessageSize; @@ -343,7 +291,6 @@ private OkHttpClientTransport( this.attributes = Attributes.newBuilder() .set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, eagAttrs).build(); this.useGetForSafeMethods = transportFactory.useGetForSafeMethods; - this.channelCredentials = channelCredentials; initTransportTracer(); } @@ -369,8 +316,7 @@ private OkHttpClientTransport( stopwatchFactory, variant, null, - tooManyPingsRunnable, - null); + tooManyPingsRunnable); this.connectingCallback = connectingCallback; this.connectedFuture = Preconditions.checkNotNull(connectedFuture, "connectedFuture"); } @@ -443,68 +389,13 @@ public void ping(final PingCallback callback, Executor executor) { } @Override - public ClientStream newStream( + public OkHttpClientStream newStream( MethodDescriptor method, Metadata headers, CallOptions callOptions, ClientStreamTracer[] tracers) { Preconditions.checkNotNull(method, "method"); Preconditions.checkNotNull(headers, "headers"); StatsTraceContext statsTraceContext = StatsTraceContext.newClientContext(tracers, getAttributes(), headers); - if (hostnameVerifier != null && socket instanceof SSLSocket - && !hostnameVerifier.verify(callOptions.getAuthority(), - ((SSLSocket) socket).getSession())) { - if (enablePerRpcAuthorityCheck) { - return new FailingClientStream(Status.UNAVAILABLE.withDescription( - String.format("HostNameVerifier verification failed for authority '%s'", - callOptions.getAuthority())), tracers); - } - } - if (socket instanceof SSLSocket && callOptions.getAuthority() != null - && channelCredentials != null && channelCredentials instanceof TlsChannelCredentials) { - Status peerVerificationStatus = null; - if (peerVerificationResults.containsKey(callOptions.getAuthority())) { - peerVerificationStatus = peerVerificationResults.get(callOptions.getAuthority()); - } else { - TrustManager x509ExtendedTrustManager; - try { - x509ExtendedTrustManager = x509ExtendedTrustManagerClass != null - ? getX509ExtendedTrustManager((TlsChannelCredentials) channelCredentials) : null; - if (x509ExtendedTrustManager == null) { - if (GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false)) { - return new FailingClientStream(Status.UNAVAILABLE.withDescription( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not " - + "available"), tracers); - } - } else { - try { - Certificate[] peerCertificates = sslSession.getPeerCertificates(); - X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length]; - for (int i = 0; i < peerCertificates.length; i++) { - x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; - } - checkServerTrustedMethod.invoke(x509ExtendedTrustManager, x509PeerCertificates, - "RSA", new SslSocketWrapper((SSLSocket) socket, callOptions.getAuthority())); - peerVerificationStatus = Status.OK; - } catch (SSLPeerUnverifiedException | InvocationTargetException - | IllegalAccessException e) { - peerVerificationStatus = Status.UNAVAILABLE.withDescription( - String.format("Failure in verifying authority '%s' against peer during rpc", - callOptions.getAuthority())).withCause(e); - } - peerVerificationResults.put(callOptions.getAuthority(), peerVerificationStatus); - } - } catch (GeneralSecurityException e) { - if (GrpcUtil.getFlag(GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK, false)) { - return new FailingClientStream(Status.UNAVAILABLE.withDescription( - "Failure getting X509ExtendedTrustManager from TlsCredentials").withCause(e), - tracers); - } - } - } - if (peerVerificationStatus != null && !peerVerificationStatus.isOk()) { - return new FailingClientStream(peerVerificationStatus, tracers); - } - } // FIXME: it is likely wrong to pass the transportTracer here as it'll exit the lock's scope synchronized (lock) { // to make @GuardedBy linter happy return new OkHttpClientStream( @@ -525,28 +416,6 @@ public ClientStream newStream( } } - private TrustManager getX509ExtendedTrustManager(TlsChannelCredentials tlsCreds) - throws GeneralSecurityException { - TrustManager[] tm = null; - // Using the same way of creating TrustManager from OkHttpChannelBuilder.sslSocketFactoryFrom() - if (tlsCreds.getTrustManagers() != null) { - tm = tlsCreds.getTrustManagers().toArray(new TrustManager[0]); - } else if (tlsCreds.getRootCertificates() != null) { - tm = CertificateUtils.createTrustManager(tlsCreds.getRootCertificates()); - } else { // else use system default - TrustManagerFactory tmf = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - tmf.init((KeyStore) null); - tm = tmf.getTrustManagers(); - } - for (TrustManager trustManager: tm) { - if (x509ExtendedTrustManagerClass.isInstance(trustManager)) { - return trustManager; - } - } - return null; - } - @GuardedBy("lock") void streamReadyToStart(OkHttpClientStream clientStream) { if (goAwayStatus != null) { @@ -662,6 +531,8 @@ public Timeout timeout() { public void close() { } }); + Socket sock; + SSLSession sslSession = null; try { // This is a hack to make sure the connection preface and initial settings to be sent out // without blocking the start. By doing this essentially prevents potential deadlock when @@ -1589,50 +1460,4 @@ public void alternateService(int streamId, String origin, ByteString protocol, S // TODO(madongfly): Deal with alternateService propagation } } - - /** - * SSLSocket wrapper that provides a fake SSLSession for handshake session. - */ - static final class SslSocketWrapper extends NoopSslSocket { - - private final SSLSession sslSession; - private final SSLSocket sslSocket; - - SslSocketWrapper(SSLSocket sslSocket, String peerHost) { - this.sslSocket = sslSocket; - this.sslSession = new FakeSslSession(peerHost); - } - - @Override - public SSLSession getHandshakeSession() { - return this.sslSession; - } - - @Override - public boolean isConnected() { - return sslSocket.isConnected(); - } - - @Override - public SSLParameters getSSLParameters() { - return sslSocket.getSSLParameters(); - } - } - - /** - * Fake SSLSession instance that provides the peer host name to verify for per-rpc check. - */ - static class FakeSslSession extends NoopSslSession { - - private final String peerHost; - - FakeSslSession(String peerHost) { - this.peerHost = peerHost; - } - - @Override - public String getPeerHost() { - return peerHost; - } - } } diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java index 8daeed42a8c..068474d70bc 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java @@ -17,7 +17,6 @@ package io.grpc.okhttp; import static com.google.common.base.Preconditions.checkArgument; -import static io.grpc.internal.CertificateUtils.createTrustManager; import com.google.common.base.Preconditions; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -426,7 +425,7 @@ static HandshakerSocketFactoryResult handshakerSocketFactoryFrom(ServerCredentia tm = tlsCreds.getTrustManagers().toArray(new TrustManager[0]); } else if (tlsCreds.getRootCertificates() != null) { try { - tm = createTrustManager(tlsCreds.getRootCertificates()); + tm = OkHttpChannelBuilder.createTrustManager(tlsCreds.getRootCertificates()); } catch (GeneralSecurityException gse) { log.log(Level.FINE, "Exception loading root certificates from credential", gse); return HandshakerSocketFactoryResult.error( diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java index 9f6f48d9fdc..daf5073992e 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java @@ -69,13 +69,10 @@ import io.grpc.Status.Code; import io.grpc.StatusException; import io.grpc.internal.AbstractStream; -import io.grpc.internal.ClientStream; import io.grpc.internal.ClientStreamListener; import io.grpc.internal.ClientTransport; -import io.grpc.internal.FailingClientStream; import io.grpc.internal.FakeClock; import io.grpc.internal.GrpcUtil; -import io.grpc.internal.InsightBuilder; import io.grpc.internal.ManagedClientTransport; import io.grpc.okhttp.OkHttpClientTransport.ClientFrameHandler; import io.grpc.okhttp.OkHttpFrameLogger.Direction; @@ -119,10 +116,6 @@ import java.util.logging.Logger; import javax.annotation.Nullable; import javax.net.SocketFactory; -import javax.net.ssl.HandshakeCompletedListener; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; import okio.Buffer; import okio.BufferedSink; import okio.BufferedSource; @@ -197,24 +190,16 @@ public void tearDown() { private void initTransport() throws Exception { startTransport( - DEFAULT_START_STREAM_ID, null, true, null, null); + DEFAULT_START_STREAM_ID, null, true, null); } private void initTransport(int startId) throws Exception { - startTransport(startId, null, true, null, null); + startTransport(startId, null, true, null); } private void startTransport(int startId, @Nullable Runnable connectingCallback, - boolean waitingForConnected, String userAgent, - HostnameVerifier hostnameVerifier) throws Exception { - startTransport(startId, connectingCallback, waitingForConnected, userAgent, hostnameVerifier, - false); - } - - private void startTransport(int startId, @Nullable Runnable connectingCallback, - boolean waitingForConnected, String userAgent, - HostnameVerifier hostnameVerifier, boolean useSslSocket) - throws Exception { + boolean waitingForConnected, String userAgent) + throws Exception { connectedFuture = SettableFuture.create(); final Ticker ticker = new Ticker() { @Override @@ -228,11 +213,7 @@ public Stopwatch get() { return Stopwatch.createUnstarted(ticker); } }; - channelBuilder.socketFactory( - new FakeSocketFactory(useSslSocket ? new MockSslSocket(socket) : socket)); - if (hostnameVerifier != null) { - channelBuilder = channelBuilder.hostnameVerifier(hostnameVerifier); - } + channelBuilder.socketFactory(new FakeSocketFactory(socket)); clientTransport = new OkHttpClientTransport( channelBuilder.buildTransportFactory(), userAgent, @@ -260,8 +241,7 @@ public void testToString() throws Exception { /*userAgent=*/ null, EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable, - null); + tooManyPingsRunnable); String s = clientTransport.toString(); assertTrue("Unexpected: " + s, s.contains("OkHttpClientTransport")); assertTrue("Unexpected: " + s, s.contains(address.toString())); @@ -279,8 +259,7 @@ public void testTransportExecutorWithTooFewThreads() throws Exception { null, EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable, - null); + tooManyPingsRunnable); clientTransport.start(transportListener); ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); verify(transportListener, timeout(TIME_OUT_MS)).transportShutdown(statusCaptor.capture()); @@ -321,7 +300,7 @@ public void close() throws SecurityException { assertThat(log.getLevel()).isEqualTo(Level.FINE); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -411,7 +390,7 @@ public void maxMessageSizeShouldBeEnforced() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -464,11 +443,11 @@ public void nextFrameThrowIoException() throws Exception { initTransport(); MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); - ClientStream stream1 = + OkHttpClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); stream1.request(1); - ClientStream stream2 = + OkHttpClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); stream2.request(1); @@ -498,7 +477,7 @@ public void nextFrameThrowIoException() throws Exception { public void nextFrameThrowsError() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -519,7 +498,7 @@ public void nextFrameThrowsError() throws Exception { public void nextFrameReturnFalse() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -537,7 +516,7 @@ public void readMessages() throws Exception { final int numMessages = 10; final String message = "Hello Client"; MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(numMessages); @@ -589,7 +568,7 @@ public void receivedDataForInvalidStreamShouldKillConnection() throws Exception public void invalidInboundHeadersCancelStream() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -614,7 +593,7 @@ public void invalidInboundHeadersCancelStream() throws Exception { public void invalidInboundTrailersPropagateToMetadata() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -634,7 +613,7 @@ public void invalidInboundTrailersPropagateToMetadata() throws Exception { public void readStatus() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); assertContainStream(3); @@ -648,7 +627,7 @@ public void readStatus() throws Exception { public void receiveReset() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); assertContainStream(3); @@ -665,7 +644,7 @@ public void receiveReset() throws Exception { public void receiveResetNoError() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); assertContainStream(3); @@ -686,7 +665,7 @@ public void receiveResetNoError() throws Exception { public void cancelStream() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); getStream(3).cancel(Status.CANCELLED); @@ -701,7 +680,7 @@ public void cancelStream() throws Exception { public void addDefaultUserAgent() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); Header userAgentHeader = new Header(GrpcUtil.USER_AGENT_KEY.name(), @@ -718,9 +697,9 @@ public void addDefaultUserAgent() throws Exception { @Test public void overrideDefaultUserAgent() throws Exception { - startTransport(3, null, true, "fakeUserAgent", null); + startTransport(3, null, true, "fakeUserAgent"); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); List
expectedHeaders = Arrays.asList(HTTP_SCHEME_HEADER, METHOD_HEADER, @@ -739,7 +718,7 @@ public void overrideDefaultUserAgent() throws Exception { public void cancelStreamForDeadlineExceeded() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); getStream(3).cancel(Status.DEADLINE_EXCEEDED); @@ -753,7 +732,7 @@ public void writeMessage() throws Exception { initTransport(); final String message = "Hello Server"; MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); InputStream input = new ByteArrayInputStream(message.getBytes(UTF_8)); @@ -768,88 +747,6 @@ public void writeMessage() throws Exception { shutdownAndVerify(); } - @Test - public void perRpcAuthoritySpecified_verificationSkippedInPlainTextConnection() - throws Exception { - initTransport(); - final String message = "Hello Server"; - MockStreamListener listener = new MockStreamListener(); - ClientStream stream = - clientTransport.newStream(method, new Metadata(), - CallOptions.DEFAULT.withAuthority("some-authority"), tracers); - stream.start(listener); - InputStream input = new ByteArrayInputStream(message.getBytes(UTF_8)); - assertEquals(12, input.available()); - stream.writeMessage(input); - stream.flush(); - verify(frameWriter, timeout(TIME_OUT_MS)) - .data(eq(false), eq(3), any(Buffer.class), eq(12 + HEADER_LENGTH)); - Buffer sentFrame = capturedBuffer.poll(); - assertEquals(createMessageFrame(message), sentFrame); - stream.cancel(Status.CANCELLED); - shutdownAndVerify(); - } - - @Test - public void perRpcAuthoritySpecified_hostnameVerification_ignoredForNonSslSocket() - throws Exception { - startTransport( - DEFAULT_START_STREAM_ID, null, true, null, - (hostname, session) -> false, false); - ClientStream unused = - clientTransport.newStream(method, new Metadata(), - CallOptions.DEFAULT.withAuthority("some-authority"), tracers); - shutdownAndVerify(); - } - - @Test - public void perRpcAuthoritySpecified_hostnameVerification_SslSocket_successCase() - throws Exception { - startTransport( - DEFAULT_START_STREAM_ID, null, true, null, - (hostname, session) -> true, true); - ClientStream unused = - clientTransport.newStream(method, new Metadata(), - CallOptions.DEFAULT.withAuthority("some-authority"), tracers); - shutdownAndVerify(); - } - - @Test - public void perRpcAuthoritySpecified_hostnameVerification_SslSocket_failureCase() - throws Exception { - OkHttpClientTransport.enablePerRpcAuthorityCheck = true; - try { - startTransport( - DEFAULT_START_STREAM_ID, null, true, null, - (hostname, session) -> false, true); - ClientStream clientStream = - clientTransport.newStream(method, new Metadata(), - CallOptions.DEFAULT.withAuthority("some-authority"), tracers); - assertThat(clientStream).isInstanceOf(FailingClientStream.class); - InsightBuilder insightBuilder = new InsightBuilder(); - clientStream.appendTimeoutInsight(insightBuilder); - assertThat(insightBuilder.toString()).contains("error=Status{code=UNAVAILABLE, " - + "description=HostNameVerifier verification failed for authority 'some-authority', " - + "cause=null}"); - shutdownAndVerify(); - } finally { - OkHttpClientTransport.enablePerRpcAuthorityCheck = false; - } - } - - @Test - public void perRpcAuthoritySpecified_hostnameVerification_SslSocket_flagDisabled() - throws Exception { - startTransport( - DEFAULT_START_STREAM_ID, null, true, null, - (hostname, session) -> false, true); - ClientStream clientStream = - clientTransport.newStream(method, new Metadata(), - CallOptions.DEFAULT.withAuthority("some-authority"), tracers); - assertThat(clientStream).isInstanceOf(OkHttpClientStream.class); - shutdownAndVerify(); - } - @Test public void transportTracer_windowSizeDefault() throws Exception { initTransport(); @@ -876,12 +773,12 @@ public void windowUpdate() throws Exception { initTransport(); MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); - ClientStream stream1 = + OkHttpClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); stream1.request(2); - ClientStream stream2 = + OkHttpClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); stream2.request(2); @@ -946,7 +843,7 @@ public void windowUpdate() throws Exception { public void windowUpdateWithInboundFlowControl() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); int messageLength = INITIAL_WINDOW_SIZE / 2 + 1; @@ -983,7 +880,7 @@ public void windowUpdateWithInboundFlowControl() throws Exception { public void outboundFlowControl() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); @@ -1029,7 +926,7 @@ public void outboundFlowControl_smallWindowSize() throws Exception { setInitialWindowSize(initialOutboundWindowSize); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); @@ -1072,7 +969,7 @@ public void outboundFlowControl_bigWindowSize() throws Exception { frameHandler().windowUpdate(0, 65535); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); @@ -1108,7 +1005,7 @@ public void outboundFlowControl_bigWindowSize() throws Exception { public void outboundFlowControlWithInitialWindowSizeChange() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); int messageLength = 20; @@ -1154,7 +1051,7 @@ public void outboundFlowControlWithInitialWindowSizeChange() throws Exception { public void outboundFlowControlWithInitialWindowSizeChangeInMiddleOfStream() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); int messageLength = 20; @@ -1189,10 +1086,10 @@ public void stopNormally() throws Exception { initTransport(); MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); - ClientStream stream1 = + OkHttpClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); - ClientStream stream2 = + OkHttpClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); assertEquals(2, activeStreamCount()); @@ -1219,11 +1116,11 @@ public void receiveGoAway() throws Exception { // start 2 streams. MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); - ClientStream stream1 = + OkHttpClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); stream1.request(1); - ClientStream stream2 = + OkHttpClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); stream2.request(1); @@ -1246,7 +1143,7 @@ public void receiveGoAway() throws Exception { // But stream 1 should be able to send. final String sentMessage = "Should I also go away?"; - ClientStream stream = getStream(3); + OkHttpClientStream stream = getStream(3); InputStream input = new ByteArrayInputStream(sentMessage.getBytes(UTF_8)); assertEquals(22, input.available()); stream.writeMessage(input); @@ -1278,7 +1175,7 @@ public void streamIdExhausted() throws Exception { initTransport(startId); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -1314,11 +1211,11 @@ public void pendingStreamSucceed() throws Exception { setMaxConcurrentStreams(1); final MockStreamListener listener1 = new MockStreamListener(); final MockStreamListener listener2 = new MockStreamListener(); - ClientStream stream1 = + OkHttpClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); // The second stream should be pending. - ClientStream stream2 = + OkHttpClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); String sentMessage = "hello"; @@ -1351,7 +1248,7 @@ public void pendingStreamCancelled() throws Exception { initTransport(); setMaxConcurrentStreams(0); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); waitForStreamPending(1); @@ -1370,11 +1267,11 @@ public void pendingStreamFailedByGoAway() throws Exception { setMaxConcurrentStreams(1); final MockStreamListener listener1 = new MockStreamListener(); final MockStreamListener listener2 = new MockStreamListener(); - ClientStream stream1 = + OkHttpClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); // The second stream should be pending. - ClientStream stream2 = + OkHttpClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); @@ -1400,7 +1297,7 @@ public void pendingStreamSucceedAfterShutdown() throws Exception { setMaxConcurrentStreams(0); final MockStreamListener listener = new MockStreamListener(); // The second stream should be pending. - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); waitForStreamPending(1); @@ -1424,15 +1321,15 @@ public void pendingStreamFailedByIdExhausted() throws Exception { final MockStreamListener listener2 = new MockStreamListener(); final MockStreamListener listener3 = new MockStreamListener(); - ClientStream stream1 = + OkHttpClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); // The second and third stream should be pending. - ClientStream stream2 = + OkHttpClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); - ClientStream stream3 = + OkHttpClientStream stream3 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream3.start(listener3); @@ -1456,7 +1353,7 @@ public void pendingStreamFailedByIdExhausted() throws Exception { public void receivingWindowExceeded() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -1508,7 +1405,7 @@ public void duplexStreamingHeadersShouldNotBeFlushed() throws Exception { private void shouldHeadersBeFlushed(boolean shouldBeFlushed) throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); verify(frameWriter, timeout(TIME_OUT_MS)).synStream( @@ -1525,7 +1422,7 @@ private void shouldHeadersBeFlushed(boolean shouldBeFlushed) throws Exception { public void receiveDataWithoutHeader() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -1548,7 +1445,7 @@ public void receiveDataWithoutHeader() throws Exception { public void receiveDataWithoutHeaderAndTrailer() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -1572,7 +1469,7 @@ public void receiveDataWithoutHeaderAndTrailer() throws Exception { public void receiveLongEnoughDataWithoutHeaderAndTrailer() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.request(1); @@ -1594,7 +1491,7 @@ public void receiveLongEnoughDataWithoutHeaderAndTrailer() throws Exception { public void receiveDataForUnknownStreamUpdateConnectionWindow() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.cancel(Status.CANCELLED); @@ -1623,7 +1520,7 @@ public void receiveDataForUnknownStreamUpdateConnectionWindow() throws Exception public void receiveWindowUpdateForUnknownStream() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); stream.cancel(Status.CANCELLED); @@ -1643,7 +1540,7 @@ public void receiveWindowUpdateForUnknownStream() throws Exception { public void shouldBeInitiallyReady() throws Exception { initTransport(); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); assertTrue(stream.isReady()); @@ -1661,7 +1558,7 @@ public void notifyOnReady() throws Exception { AbstractStream.TransportState.DEFAULT_ONREADY_THRESHOLD - HEADER_LENGTH - 1; setInitialWindowSize(0); MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); assertTrue(stream.isReady()); @@ -1814,7 +1711,7 @@ public void shutdownDuringConnecting() throws Exception { DEFAULT_START_STREAM_ID, connectingCallback, false, - null, null); + null); clientTransport.shutdown(SHUTDOWN_REASON); delayed.set(null); shutdownAndVerify(); @@ -1829,8 +1726,7 @@ public void invalidAuthorityPropagates() { "userAgent", EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable, - null); + tooManyPingsRunnable); String host = clientTransport.getOverridenHost(); int port = clientTransport.getOverridenPort(); @@ -1848,8 +1744,7 @@ public void unreachableServer() throws Exception { "userAgent", EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable, - null); + tooManyPingsRunnable); ManagedClientTransport.Listener listener = mock(ManagedClientTransport.Listener.class); clientTransport.start(listener); @@ -1879,8 +1774,7 @@ public void customSocketFactory() throws Exception { "userAgent", EAG_ATTRS, NO_PROXY, - tooManyPingsRunnable, - null); + tooManyPingsRunnable); ManagedClientTransport.Listener listener = mock(ManagedClientTransport.Listener.class); clientTransport.start(listener); @@ -1905,8 +1799,7 @@ public void proxy_200() throws Exception { .setTargetAddress(targetAddress) .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) .build(), - tooManyPingsRunnable, - null); + tooManyPingsRunnable); clientTransport.start(transportListener); Socket sock = serverSocket.accept(); @@ -1955,8 +1848,7 @@ public void proxy_500() throws Exception { .setTargetAddress(targetAddress) .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) .build(), - tooManyPingsRunnable, - null); + tooManyPingsRunnable); clientTransport.start(transportListener); Socket sock = serverSocket.accept(); @@ -2004,8 +1896,7 @@ public void proxy_immediateServerClose() throws Exception { .setTargetAddress(targetAddress) .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) .build(), - tooManyPingsRunnable, - null); + tooManyPingsRunnable); clientTransport.start(transportListener); Socket sock = serverSocket.accept(); @@ -2036,8 +1927,7 @@ public void proxy_serverHangs() throws Exception { .setTargetAddress(targetAddress) .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) .build(), - tooManyPingsRunnable, - null); + tooManyPingsRunnable); clientTransport.proxySocketTimeout = 10; clientTransport.start(transportListener); @@ -2104,13 +1994,13 @@ public void goAway_streamListenerRpcProgress() throws Exception { MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); MockStreamListener listener3 = new MockStreamListener(); - ClientStream stream1 = + OkHttpClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); - ClientStream stream2 = + OkHttpClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); - ClientStream stream3 = + OkHttpClientStream stream3 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream3.start(listener3); waitForStreamPending(1); @@ -2144,13 +2034,13 @@ public void reset_streamListenerRpcProgress() throws Exception { MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); MockStreamListener listener3 = new MockStreamListener(); - ClientStream stream1 = + OkHttpClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); - ClientStream stream2 = + OkHttpClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); - ClientStream stream3 = + OkHttpClientStream stream3 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream3.start(listener3); @@ -2186,13 +2076,13 @@ public void shutdownNow_streamListenerRpcProgress() throws Exception { MockStreamListener listener1 = new MockStreamListener(); MockStreamListener listener2 = new MockStreamListener(); MockStreamListener listener3 = new MockStreamListener(); - ClientStream stream1 = + OkHttpClientStream stream1 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream1.start(listener1); - ClientStream stream2 = + OkHttpClientStream stream2 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream2.start(listener2); - ClientStream stream3 = + OkHttpClientStream stream3 = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream3.start(listener3); waitForStreamPending(1); @@ -2218,12 +2108,10 @@ public void finishedStreamRemovedFromInUseState() throws Exception { setMaxConcurrentStreams(1); final MockStreamListener listener = new MockStreamListener(); OkHttpClientStream stream = - (OkHttpClientStream) clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, - tracers); + clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); OkHttpClientStream pendingStream = - (OkHttpClientStream) clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, - tracers); + clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); pendingStream.start(listener); waitForStreamPending(1); clientTransport.finishStream(stream.transportState().id(), Status.OK, PROCESSED, @@ -2263,7 +2151,7 @@ private void waitForStreamPending(int expected) throws Exception { private void assertNewStreamFail() throws Exception { MockStreamListener listener = new MockStreamListener(); - ClientStream stream = + OkHttpClientStream stream = clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); stream.start(listener); listener.waitUntilStreamClosed(); @@ -2494,124 +2382,6 @@ public InputStream getInputStream() { } } - private static class MockSslSocket extends SSLSocket { - private Socket delegate; - - MockSslSocket(Socket socket) { - delegate = socket; - } - - @Override - public String[] getSupportedCipherSuites() { - return new String[0]; - } - - @Override - public String[] getEnabledCipherSuites() { - return new String[0]; - } - - @Override - public void setEnabledCipherSuites(String[] suites) { - - } - - @Override - public String[] getSupportedProtocols() { - return new String[0]; - } - - @Override - public String[] getEnabledProtocols() { - return new String[0]; - } - - @Override - public void setEnabledProtocols(String[] protocols) { - - } - - @Override - public SSLSession getSession() { - return null; - } - - @Override - public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { - - } - - @Override - public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { - - } - - @Override - public void startHandshake() throws IOException { - - } - - @Override - public void setUseClientMode(boolean mode) { - - } - - @Override - public boolean getUseClientMode() { - return false; - } - - @Override - public void setNeedClientAuth(boolean need) { - - } - - @Override - public boolean getNeedClientAuth() { - return false; - } - - @Override - public void setWantClientAuth(boolean want) { - - } - - @Override - public boolean getWantClientAuth() { - return false; - } - - @Override - public void setEnableSessionCreation(boolean flag) { - - } - - @Override - public boolean getEnableSessionCreation() { - return false; - } - - @Override - public synchronized void close() throws IOException { - delegate.close(); - } - - @Override - public SocketAddress getLocalSocketAddress() { - return delegate.getLocalSocketAddress(); - } - - @Override - public OutputStream getOutputStream() throws IOException { - return delegate.getOutputStream(); - } - - @Override - public InputStream getInputStream() throws IOException { - return delegate.getInputStream(); - } - } - static class PingCallbackImpl implements ClientTransport.PingCallback { int invocationCount; long roundTripTime; diff --git a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java index 64512f07cde..a21360a89ba 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java @@ -18,10 +18,8 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static org.junit.Assert.fail; import com.google.common.base.Throwables; -import io.grpc.CallOptions; import io.grpc.ChannelCredentials; import io.grpc.ConnectivityState; import io.grpc.ManagedChannel; @@ -34,34 +32,18 @@ import io.grpc.TlsServerCredentials; import io.grpc.internal.testing.TestUtils; import io.grpc.okhttp.internal.Platform; -import io.grpc.stub.ClientCalls; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.TlsTesting; import io.grpc.testing.protobuf.SimpleRequest; import io.grpc.testing.protobuf.SimpleResponse; import io.grpc.testing.protobuf.SimpleServiceGrpc; -import io.grpc.util.CertificateUtils; import java.io.IOException; import java.io.InputStream; -import java.net.Socket; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.Optional; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedTrustManager; -import javax.net.ssl.X509TrustManager; -import javax.security.auth.x500.X500Principal; - -import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; import org.junit.Assume; import org.junit.Before; import org.junit.Rule; @@ -71,7 +53,6 @@ /** Verify OkHttp's TLS integration. */ @RunWith(JUnit4.class) -@IgnoreJRERequirement public class TlsTest { @Rule public final GrpcCleanupRule grpcCleanupRule = new GrpcCleanupRule(); @@ -111,108 +92,6 @@ public void basicTls_succeeds() throws Exception { SimpleServiceGrpc.newBlockingStub(channel).unaryRpc(SimpleRequest.getDefaultInstance()); } - @Test - public void perRpcAuthorityOverride_checkServerTrustedIsCalled() throws Exception { - OkHttpClientTransport.enablePerRpcAuthorityCheck = true; - try { - ServerCredentials serverCreds; - try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); - InputStream serverPrivateKey = TlsTesting.loadCert("server1.key")) { - serverCreds = TlsServerCredentials.newBuilder() - .keyManager(serverCert, serverPrivateKey) - .build(); - } - ChannelCredentials channelCreds; - FakeX509ExtendedTrustManager fakeTrustManager; - try (InputStream caCert = TlsTesting.loadCert("ca.pem")) { - X509ExtendedTrustManager x509ExtendedTrustManager = - (X509ExtendedTrustManager) getX509ExtendedTrustManager(caCert).get(); - fakeTrustManager = new FakeX509ExtendedTrustManager(x509ExtendedTrustManager); - channelCreds = TlsChannelCredentials.newBuilder() - .trustManager(fakeTrustManager) - .build(); - } - Server server = grpcCleanupRule.register(server(serverCreds)); - ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); - - ClientCalls.blockingUnaryCall(channel, SimpleServiceGrpc.getUnaryRpcMethod(), - CallOptions.DEFAULT.withAuthority("foo.test.google.fr"), - SimpleRequest.getDefaultInstance()); - assertThat(fakeTrustManager.checkServerTrustedCalled).isTrue(); - } finally { - OkHttpClientTransport.enablePerRpcAuthorityCheck = false; - } - } - - /** - * This negative test simulates the absence of X509ExtendedTrustManager while still using the - * real trust manager for the connection handshake to happen. - */ - @Test - public void perRpcAuthorityOverride_tlsCreds_noX509ExtendedTrustManager_fails() - throws Exception { - OkHttpClientTransport.enablePerRpcAuthorityCheck = true; - try { - ServerCredentials serverCreds; - try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); - InputStream serverPrivateKey = TlsTesting.loadCert("server1.key")) { - serverCreds = TlsServerCredentials.newBuilder() - .keyManager(serverCert, serverPrivateKey) - .build(); - } - ChannelCredentials channelCreds; - try (InputStream caCert = TlsTesting.loadCert("ca.pem")) { - X509TrustManager x509ExtendedTrustManager = - (X509TrustManager) getX509ExtendedTrustManager(caCert).get(); - channelCreds = TlsChannelCredentials.newBuilder() - .trustManager(new FakeTrustManager(x509ExtendedTrustManager)) - .build(); - } - Server server = grpcCleanupRule.register(server(serverCreds)); - ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); - - try { - ClientCalls.blockingUnaryCall(channel, SimpleServiceGrpc.getUnaryRpcMethod(), - CallOptions.DEFAULT.withAuthority("foo.test.google.in"), - SimpleRequest.getDefaultInstance()); - fail("Expected exception for authority not matching cert name."); - } catch (StatusRuntimeException ex) { - assertThat(ex.getStatus().getCode()).isEqualTo(Status.Code.UNAVAILABLE); - assertThat(ex.getStatus().getDescription()).isEqualTo( - "Can't allow authority override in rpc when X509ExtendedTrustManager is not " - + "available"); - } - } finally { - OkHttpClientTransport.enablePerRpcAuthorityCheck = false; - } - } - - @Test - public void perRpcAuthorityOverride_tlsCreds_noX509ExtendedTrustManager_flagDisabled() - throws Exception { - ServerCredentials serverCreds; - try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); - InputStream serverPrivateKey = TlsTesting.loadCert("server1.key")) { - serverCreds = TlsServerCredentials.newBuilder() - .keyManager(serverCert, serverPrivateKey) - .build(); - } - ChannelCredentials channelCreds; - try (InputStream caCert = TlsTesting.loadCert("ca.pem")) { - X509TrustManager x509ExtendedTrustManager = - (X509TrustManager) getX509ExtendedTrustManager(caCert).get(); - channelCreds = TlsChannelCredentials.newBuilder() - .trustManager(new FakeTrustManager(x509ExtendedTrustManager)) - .build(); - } - Server server = grpcCleanupRule.register(server(serverCreds)); - ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); - - ClientCalls.blockingUnaryCall(channel, SimpleServiceGrpc.getUnaryRpcMethod(), - CallOptions.DEFAULT.withAuthority("foo.test.google.in"), - SimpleRequest.getDefaultInstance()); - } - @Test public void mtls_succeeds() throws Exception { ServerCredentials serverCreds; @@ -403,103 +282,6 @@ public void hostnameVerifierFails_fails() assertThat(status.getCause()).isInstanceOf(SSLPeerUnverifiedException.class); } - /** Used to simulate the case of X509ExtendedTrustManager not present. */ - private static class FakeTrustManager implements X509TrustManager { - - private final X509TrustManager delegate; - - public FakeTrustManager(X509TrustManager x509ExtendedTrustManager) { - this.delegate = x509ExtendedTrustManager; - } - - @Override - public void checkClientTrusted(X509Certificate[] x509Certificates, String s) - throws CertificateException { - delegate.checkClientTrusted(x509Certificates, s); - } - - @Override - public void checkServerTrusted(X509Certificate[] x509Certificates, String s) - throws CertificateException { - delegate.checkServerTrusted(x509Certificates, s); - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return delegate.getAcceptedIssuers(); - } - } - - /** Used to capture the fact that checkServerTrusted has been called for the per-rpc authority - * verification. */ - @IgnoreJRERequirement - private static class FakeX509ExtendedTrustManager extends X509ExtendedTrustManager { - private final X509ExtendedTrustManager delegate; - private boolean checkServerTrustedCalled; - - private FakeX509ExtendedTrustManager(X509ExtendedTrustManager delegate) { - this.delegate = delegate; - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) - throws CertificateException { - delegate.checkServerTrusted(chain, authType, socket); - this.checkServerTrustedCalled = true; - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) - throws CertificateException { - delegate.checkServerTrusted(chain, authType, engine); - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - delegate.checkServerTrusted(chain, authType); - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) { - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) { - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - } - - private static Optional getX509ExtendedTrustManager(InputStream rootCerts) - throws GeneralSecurityException { - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - try { - ks.load(null, null); - } catch (IOException ex) { - // Shouldn't really happen, as we're not loading any data. - throw new GeneralSecurityException(ex); - } - X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); - for (X509Certificate cert : certs) { - X500Principal principal = cert.getSubjectX500Principal(); - ks.setCertificateEntry(principal.getName("RFC2253"), cert); - } - - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(ks); - return Arrays.stream(trustManagerFactory.getTrustManagers()) - .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); - } - private static Server server(ServerCredentials creds) throws IOException { return OkHttpServerBuilder.forPort(0, creds) .directExecutor() From 1d6f46f8297c4ad745f3206aa6adc62b52f46b3e Mon Sep 17 00:00:00 2001 From: Kannan J Date: Thu, 30 Jan 2025 17:20:41 +0000 Subject: [PATCH 63/70] Fix style --- .../io/grpc/internal/AuthorityVerifier.java | 17 +++++++++++++++++ .../java/io/grpc/internal/GrpcAttributes.java | 1 + .../java/io/grpc/netty/ProtocolNegotiators.java | 7 ------- .../io/grpc/netty/X509AuthorityVerifier.java | 16 ++++++++++++++++ 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/AuthorityVerifier.java b/core/src/main/java/io/grpc/internal/AuthorityVerifier.java index b24d81e81dc..e6164a7dc4d 100644 --- a/core/src/main/java/io/grpc/internal/AuthorityVerifier.java +++ b/core/src/main/java/io/grpc/internal/AuthorityVerifier.java @@ -1,7 +1,24 @@ +/* + * Copyright 2025 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.grpc.internal; import io.grpc.Status; +/** Verifier for the outgoing authority pseudo-header against peer cert. */ public interface AuthorityVerifier { Status verifyAuthority(String authority); } diff --git a/core/src/main/java/io/grpc/internal/GrpcAttributes.java b/core/src/main/java/io/grpc/internal/GrpcAttributes.java index ad808bef6da..f95f9b9dab8 100644 --- a/core/src/main/java/io/grpc/internal/GrpcAttributes.java +++ b/core/src/main/java/io/grpc/internal/GrpcAttributes.java @@ -44,5 +44,6 @@ public final class GrpcAttributes { public static final Attributes.Key ATTR_AUTHORITY_VERIFIER = Attributes.Key.create("io.grpc.internal.GrpcAttributes.authorityVerifier"); + private GrpcAttributes() {} } diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 82f06dd8fa9..f06f7c03e8d 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -71,16 +71,11 @@ import io.netty.handler.ssl.SslProvider; import io.netty.util.AsciiString; import java.io.ByteArrayInputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.SocketAddress; import java.net.URI; import java.nio.channels.ClosedChannelException; import java.security.GeneralSecurityException; import java.security.KeyStore; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.EnumSet; import java.util.List; @@ -88,12 +83,10 @@ import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; -import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; -import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; diff --git a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java index 169da4b7bf7..7635f440791 100644 --- a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java +++ b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java @@ -1,3 +1,19 @@ +/* + * Copyright 2025 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.grpc.netty; import io.grpc.Status; From 42058ead12f6a3a719f5e6441431d020cc1b86f8 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Fri, 31 Jan 2025 13:35:08 +0000 Subject: [PATCH 64/70] Revert unintended changes. --- examples/build.gradle | 2 +- examples/example-tls/build.gradle | 2 +- .../helloworldtls/HelloWorldClientTls.java | 10 +-- .../io/grpc/netty/NettyClientHandler.java | 18 +++- .../io/grpc/netty/X509AuthorityVerifier.java | 13 ++- .../grpc/netty/NettyClientTransportTest.java | 83 ++++++++++--------- 6 files changed, 76 insertions(+), 52 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index 761fd9d9e6a..d4991f02f43 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -45,7 +45,7 @@ dependencies { protobuf { protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" } plugins { - grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.70.0" } + grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } } generateProtoTasks { all()*.plugins { grpc {} } diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 4af0e8565e5..6ae5b2bb816 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -34,7 +34,7 @@ dependencies { protobuf { protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" } plugins { - grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.70.0" } + grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } } generateProtoTasks { all()*.plugins { grpc {} } diff --git a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java index 4857922107f..4ff7e23a299 100644 --- a/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java +++ b/examples/example-tls/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java @@ -16,9 +16,6 @@ package io.grpc.examples.helloworldtls; -import static io.grpc.examples.helloworld.GreeterGrpc.getSayHelloMethod; - -import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.Grpc; import io.grpc.ManagedChannel; @@ -28,7 +25,6 @@ import io.grpc.examples.helloworld.HelloReply; import io.grpc.examples.helloworld.HelloRequest; import java.io.File; -import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -56,11 +52,7 @@ public void greet(String name) { HelloRequest request = HelloRequest.newBuilder().setName(name).build(); HelloReply response; try { - CallOptions callOptions = blockingStub.getCallOptions() - .withAuthority("moo.test.goog.fr"); - response = io.grpc.stub.ClientCalls.blockingUnaryCall( - blockingStub.getChannel(), getSayHelloMethod(), - callOptions, request); + response = blockingStub.sayHello(request); } catch (StatusRuntimeException e) { logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); return; diff --git a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java index 295cd3cb829..c4cf38b897b 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java @@ -586,6 +586,17 @@ protected boolean isGracefulShutdownComplete() { && ((StreamBufferingEncoder) encoder()).numBufferedStreams() == 0; } + private String getAuthorityPseudoHeader(Http2Headers http2Headers) { + if (http2Headers instanceof GrpcHttp2OutboundHeaders) { + return ((GrpcHttp2OutboundHeaders) http2Headers).getAuthority(); + } + try { + return http2Headers.authority().toString(); + } catch (UnsupportedOperationException e) { + return null; + } + } + /** * Attempts to create a new stream from the given command. If there are too many active streams, * the creation request is queued. @@ -602,15 +613,16 @@ private void createStream(CreateStreamCommand command, ChannelPromise promise) return; } - String authority = ((GrpcHttp2OutboundHeaders) command.headers()).getAuthority(); + String authority = getAuthorityPseudoHeader(command.headers()); if (authority != null) { Status authorityVerificationStatus = peerVerificationResults.get(authority); - if (authorityVerificationStatus == null) { + if (authorityVerificationStatus == null && attributes != null + && attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) != null) { authorityVerificationStatus = attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) .verifyAuthority(((GrpcHttp2OutboundHeaders) command.headers()).getAuthority()); peerVerificationResults.put(authority, authorityVerificationStatus); } - if (!authorityVerificationStatus.isOk()) { + if (authorityVerificationStatus != null && !authorityVerificationStatus.isOk()) { logger.log(Level.WARNING, String.format("%s.%s", authorityVerificationStatus.getDescription(), enablePerRpcAuthorityCheck ? "" : "This will be an error in the future."), diff --git a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java index 7635f440791..b295fecdde8 100644 --- a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java +++ b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java @@ -65,7 +65,9 @@ public Status verifyAuthority(@Nonnull String authority) { } Status peerVerificationStatus; try { - verifyAuthorityAllowedForPeerCert(authority); + // Because the authority pseudo-header can contain a port number: + // https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2.3com + verifyAuthorityAllowedForPeerCert(removeAnyPortNumber(authority)); peerVerificationStatus = Status.OK; } catch (SSLPeerUnverifiedException | CertificateException | InvocationTargetException | IllegalAccessException | IllegalStateException e) { @@ -76,6 +78,15 @@ public Status verifyAuthority(@Nonnull String authority) { return peerVerificationStatus; } + private String removeAnyPortNumber(String authority) { + int closingSquareBracketIndex = authority.lastIndexOf(']'); + int portNumberSeperatorColonIndex = authority.lastIndexOf(':'); + if (portNumberSeperatorColonIndex > closingSquareBracketIndex) { + return authority.substring(0, portNumberSeperatorColonIndex); + } + return authority; + } + private void verifyAuthorityAllowedForPeerCert(String authority) throws SSLPeerUnverifiedException, CertificateException, InvocationTargetException, IllegalAccessException { diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index f2c238dacba..1e52cd1f592 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -37,9 +37,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -139,8 +137,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.mockito.AdditionalAnswers; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -955,47 +951,58 @@ public void authorityOverrideInCallOptions_matchesServerPeerHost_newStreamCreati } } + // Without removing the port number part that {@link X509AuthorityVerifier} does, there will be a + // java.security.cert.CertificateException: Illegal given domain name: foo.test.google.fr:12345 @Test - public void authorityOverrideInCallOptions_lruCache() - throws IOException, InterruptedException, GeneralSecurityException, ExecutionException, - TimeoutException { + public void authorityOverrideInCallOptions_portNumberInAuthority_isStrippedForPeerVerification() + throws IOException, InterruptedException, GeneralSecurityException, ExecutionException, + TimeoutException { NettyClientHandler.enablePerRpcAuthorityCheck = true; try { startServer(); - ProtocolNegotiator mockNegotiator = - mock(ProtocolNegotiator.class, AdditionalAnswers.delegatesTo(newNegotiator())); - NettyClientTransport transport = newTransport(mockNegotiator); + NettyClientTransport transport = newTransport(newNegotiator()); SettableFuture connected = SettableFuture.create(); FakeClientTransportListener fakeClientTransportListener = - new FakeClientTransportListener(connected); + new FakeClientTransportListener(connected); callMeMaybe(transport.start(fakeClientTransportListener)); connected.get(10, TimeUnit.SECONDS); assertThat(fakeClientTransportListener.isConnected()).isTrue(); - ArgumentCaptor authorityArgumentCaptor = ArgumentCaptor.forClass(String.class); - new Rpc(transport, new Metadata(), "foo-0.test.google.fr").halfClose() - .waitForResponse(); - // Should use cache. - new Rpc(transport, new Metadata(), "foo-0.test.google.fr").waitForResponse(); - for (int i = 1; i < 100; i++) { - // Should call verifyAuthority each time here and the cache grows with each call. - new Rpc(transport, new Metadata(), "foo-" + i + ".test.google.fr").halfClose() - .waitForResponse(); - } - // Cache is full at this point. Eviction occurs here for foo-0.test.google.fr. - new Rpc(transport, new Metadata(), "foo-100.test.google.fr").halfClose() - .waitForResponse(); - // verifyAuthority call happens for foo-0.test.google.fr. - new Rpc(transport, new Metadata(), "foo-0.test.google.fr").halfClose() - .waitForResponse(); - verify(mockNegotiator, times(102)).verifyAuthority( - authorityArgumentCaptor.capture()); - List authorityValues = authorityArgumentCaptor.getAllValues(); - for (int i = 0; i < 100; i++) { - assertThat(authorityValues.get(i)).isEqualTo("foo-" + i + ".test.google.fr"); - } - assertThat(authorityValues.get(100)).isEqualTo("foo-100.test.google.fr"); - assertThat(authorityValues.get(101)).isEqualTo("foo-0.test.google.fr"); + new Rpc(transport, new Metadata(), "foo.test.google.fr:12345").waitForResponse(); + } finally { + NettyClientHandler.enablePerRpcAuthorityCheck = false;; + } + } + + @Test + public void authorityOverrideInCallOptions_portNumberAndIpv6_isStrippedForPeerVerification() + throws IOException, InterruptedException, GeneralSecurityException, ExecutionException, + TimeoutException { + NettyClientHandler.enablePerRpcAuthorityCheck = true; + try { + startServer(); + NettyClientTransport transport = newTransport(newNegotiator()); + SettableFuture connected = SettableFuture.create(); + FakeClientTransportListener fakeClientTransportListener = + new FakeClientTransportListener(connected); + callMeMaybe(transport.start(fakeClientTransportListener)); + connected.get(10, TimeUnit.SECONDS); + assertThat(fakeClientTransportListener.isConnected()).isTrue(); + + new Rpc(transport, new Metadata(), "[2001:db8:3333:4444:5555:6666:1.2.3.4]:12345") + .waitForResponse(); + } catch (ExecutionException ex) { + Status status = ((StatusException) ex.getCause()).getStatus(); + assertThat(status.getDescription()).isEqualTo("Peer hostname verification during rpc " + + "failed for authority '[2001:db8:3333:4444:5555:6666:1.2.3.4]:12345'"); + assertThat(status.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(((InvocationTargetException) ex.getCause().getCause()).getTargetException()) + .isInstanceOf(CertificateException.class); + // Port number is removed by {@link X509AuthorityVerifier}. + assertThat(((InvocationTargetException) ex.getCause().getCause()).getTargetException() + .getMessage()).isEqualTo( + "No subject alternative names matching IP address 2001:db8:3333:4444:5555:6666:1.2.3.4 " + + "found"); } finally { NettyClientHandler.enablePerRpcAuthorityCheck = false;; } @@ -1182,9 +1189,11 @@ private static class Rpc { Rpc(NettyClientTransport transport, Metadata headers, String authorityOverride) { stream = transport.newStream( - METHOD, headers, authorityOverride != null - ? CallOptions.DEFAULT.withAuthority(authorityOverride) : CallOptions.DEFAULT, + METHOD, headers, CallOptions.DEFAULT, new ClientStreamTracer[]{ new ClientStreamTracer() {} }); + if (authorityOverride != null) { + stream.setAuthority(authorityOverride); + } stream.start(listener); stream.request(1); stream.writeMessage(new ByteArrayInputStream(MESSAGE.getBytes(UTF_8))); From cd93d4e95c7862a35bd0fa712be1de3649adbd6a Mon Sep 17 00:00:00 2001 From: Kannan J Date: Fri, 31 Jan 2025 14:15:09 +0000 Subject: [PATCH 65/70] Revert unintended changes. --- examples/example-tls/build.gradle | 4 +- .../io/grpc/netty/NettyClientTransport.java | 2 - .../io/grpc/netty/ProtocolNegotiator.java | 8 --- .../io/grpc/netty/ProtocolNegotiators.java | 72 +++++++++---------- .../io/grpc/netty/X509AuthorityVerifier.java | 1 + 5 files changed, 38 insertions(+), 49 deletions(-) diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 6ae5b2bb816..87e37c5a3b1 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -71,6 +71,8 @@ application { applicationDistribution.into('bin') { from(helloWorldTlsServer) from(helloWorldTlsClient) - fileMode = 0755 + filePermissions { + unix(0755) + } } } diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index 83b6c46caf9..86d8991ba95 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -63,7 +63,6 @@ import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; import javax.annotation.Nullable; /** @@ -106,7 +105,6 @@ class NettyClientTransport implements ConnectionClientTransport { private final ChannelLogger channelLogger; private final boolean useGetForSafeMethods; private final Ticker ticker; - private final Logger logger = Logger.getLogger(NettyClientTransport.class.getName()); NettyClientTransport( diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java index a91b361b8f7..4332fdf2919 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java @@ -16,7 +16,6 @@ package io.grpc.netty; -import io.grpc.Status; import io.grpc.internal.ObjectPool; import io.netty.channel.ChannelHandler; import io.netty.util.AsciiString; @@ -65,11 +64,4 @@ interface ServerFactory { ProtocolNegotiator newNegotiator(ObjectPool offloadExecutorPool); } - /** - * Verify the authority against peer if applicable depending on the transport credential type. - */ - default Status verifyAuthority(String authority) { - return Status.UNAVAILABLE.withDescription("Per-rpc authority verification not implemented by " - + "the protocol negotiator"); - } } diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index f06f7c03e8d..2def192c36f 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -16,11 +16,11 @@ package io.grpc.netty; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import com.google.errorprone.annotations.ForOverride; import io.grpc.Attributes; import io.grpc.CallCredentials; @@ -46,7 +46,6 @@ import io.grpc.internal.GrpcUtil; import io.grpc.internal.NoopSslSession; import io.grpc.internal.ObjectPool; -import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; @@ -61,7 +60,6 @@ import io.netty.handler.codec.http2.Http2ClientUpgradeCodec; import io.netty.handler.proxy.HttpProxyHandler; import io.netty.handler.proxy.ProxyConnectionEvent; -import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.OpenSslEngine; import io.netty.handler.ssl.SslContext; @@ -225,15 +223,15 @@ public static FromServerCredentialsResult from(ServerCredentials creds) { } // else use system default switch (tlsCreds.getClientAuth()) { case OPTIONAL: - builder.clientAuth(ClientAuth.OPTIONAL); + builder.clientAuth(io.netty.handler.ssl.ClientAuth.OPTIONAL); break; case REQUIRE: - builder.clientAuth(ClientAuth.REQUIRE); + builder.clientAuth(io.netty.handler.ssl.ClientAuth.REQUIRE); break; case NONE: - builder.clientAuth(ClientAuth.NONE); + builder.clientAuth(io.netty.handler.ssl.ClientAuth.NONE); break; default: @@ -289,17 +287,17 @@ private FromChannelCredentialsResult(ProtocolNegotiator.ClientFactory negotiator public static FromChannelCredentialsResult error(String error) { return new FromChannelCredentialsResult( - null, null, checkNotNull(error, "error")); + null, null, Preconditions.checkNotNull(error, "error")); } public static FromChannelCredentialsResult negotiator( ProtocolNegotiator.ClientFactory factory) { return new FromChannelCredentialsResult( - checkNotNull(factory, "factory"), null, null); + Preconditions.checkNotNull(factory, "factory"), null, null); } public FromChannelCredentialsResult withCallCredentials(CallCredentials callCreds) { - checkNotNull(callCreds, "callCreds"); + Preconditions.checkNotNull(callCreds, "callCreds"); if (error != null) { return this; } @@ -320,11 +318,11 @@ private FromServerCredentialsResult(ProtocolNegotiator.ServerFactory negotiator, } public static FromServerCredentialsResult error(String error) { - return new FromServerCredentialsResult(null, checkNotNull(error, "error")); + return new FromServerCredentialsResult(null, Preconditions.checkNotNull(error, "error")); } public static FromServerCredentialsResult negotiator(ProtocolNegotiator.ServerFactory factory) { - return new FromServerCredentialsResult(checkNotNull(factory, "factory"), null); + return new FromServerCredentialsResult(Preconditions.checkNotNull(factory, "factory"), null); } } @@ -339,7 +337,7 @@ private static final class FixedProtocolNegotiatorServerFactory public FixedProtocolNegotiatorServerFactory(ProtocolNegotiator protocolNegotiator) { this.protocolNegotiator = - checkNotNull(protocolNegotiator, "protocolNegotiator"); + Preconditions.checkNotNull(protocolNegotiator, "protocolNegotiator"); } @Override @@ -381,7 +379,7 @@ static final class TlsProtocolNegotiatorServerFactory private final SslContext sslContext; public TlsProtocolNegotiatorServerFactory(SslContext sslContext) { - this.sslContext = checkNotNull(sslContext, "sslContext"); + this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext"); } @Override @@ -396,7 +394,7 @@ public ProtocolNegotiator newNegotiator(ObjectPool offloadEx */ public static ProtocolNegotiator serverTls(final SslContext sslContext, final ObjectPool executorPool) { - checkNotNull(sslContext, "sslContext"); + Preconditions.checkNotNull(sslContext, "sslContext"); final Executor executor; if (executorPool != null) { // The handlers here can out-live the {@link ProtocolNegotiator}. @@ -446,8 +444,8 @@ static final class ServerTlsHandler extends ChannelInboundHandlerAdapter { ServerTlsHandler(ChannelHandler next, SslContext sslContext, final ObjectPool executorPool) { - this.sslContext = checkNotNull(sslContext, "sslContext"); - this.next = checkNotNull(next, "next"); + this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext"); + this.next = Preconditions.checkNotNull(next, "next"); if (executorPool != null) { this.executor = executorPool.getObject(); } @@ -504,8 +502,8 @@ private void fireProtocolNegotiationEvent(ChannelHandlerContext ctx, SSLSession public static ProtocolNegotiator httpProxy(final SocketAddress proxyAddress, final @Nullable String proxyUsername, final @Nullable String proxyPassword, final ProtocolNegotiator negotiator) { - checkNotNull(negotiator, "negotiator"); - checkNotNull(proxyAddress, "proxyAddress"); + Preconditions.checkNotNull(negotiator, "negotiator"); + Preconditions.checkNotNull(proxyAddress, "proxyAddress"); final AsciiString scheme = negotiator.scheme(); class ProxyNegotiator implements ProtocolNegotiator { @Override @@ -551,7 +549,7 @@ public ProxyProtocolNegotiationHandler( ChannelHandler next, ChannelLogger negotiationLogger) { super(next, negotiationLogger); - this.address = checkNotNull(address, "address"); + this.address = Preconditions.checkNotNull(address, "address"); this.userName = userName; this.password = password; } @@ -582,7 +580,7 @@ static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator { public ClientTlsProtocolNegotiator(SslContext sslContext, ObjectPool executorPool, Optional handshakeCompleteRunnable, X509TrustManager x509ExtendedTrustManager) { - this.sslContext = checkNotNull(sslContext, "sslContext"); + this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext"); this.executorPool = executorPool; if (this.executorPool != null) { this.executor = this.executorPool.getObject(); @@ -630,7 +628,6 @@ static final class ClientTlsHandler extends ProtocolNegotiationHandler { private final SslContext sslContext; private final String host; private final int port; - private final ClientTlsProtocolNegotiator clientTlsProtocolNegotiator; private Executor executor; private final Optional handshakeCompleteRunnable; private final X509TrustManager x509ExtendedTrustManager; @@ -642,13 +639,12 @@ static final class ClientTlsHandler extends ProtocolNegotiationHandler { ClientTlsProtocolNegotiator clientTlsProtocolNegotiator, X509TrustManager x509ExtendedTrustManager) { super(next, negotiationLogger); - this.sslContext = checkNotNull(sslContext, "sslContext"); + this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext"); HostPort hostPort = parseAuthority(authority); this.host = hostPort.host; this.port = hostPort.port; this.executor = executor; this.handshakeCompleteRunnable = handshakeCompleteRunnable; - this.clientTlsProtocolNegotiator = clientTlsProtocolNegotiator; this.x509ExtendedTrustManager = x509ExtendedTrustManager; } @@ -725,7 +721,7 @@ private void propagateTlsComplete(ChannelHandlerContext ctx, SSLSession session) @VisibleForTesting static HostPort parseAuthority(String authority) { - URI uri = GrpcUtil.authorityToUri(checkNotNull(authority, "authority")); + URI uri = GrpcUtil.authorityToUri(Preconditions.checkNotNull(authority, "authority")); String host; int port; if (uri.getHost() != null) { @@ -748,7 +744,7 @@ static HostPort parseAuthority(String authority) { /** * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will - * be negotiated, the {@code handler} is added and writes to the {@link Channel} + * be negotiated, the {@code handler} is added and writes to the {@link io.netty.channel.Channel} * may happen immediately, even before the TLS Handshake is complete. * @param executorPool a dedicated {@link Executor} pool for time-consuming TLS tasks */ @@ -761,7 +757,7 @@ public static ProtocolNegotiator tls(SslContext sslContext, /** * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will - * be negotiated, the {@code handler} is added and writes to the {@link Channel} + * be negotiated, the {@code handler} is added and writes to the {@link io.netty.channel.Channel} * may happen immediately, even before the TLS Handshake is complete. */ public static ProtocolNegotiator tls(SslContext sslContext, @@ -783,7 +779,7 @@ static final class TlsProtocolNegotiatorClientFactory public TlsProtocolNegotiatorClientFactory(SslContext sslContext, X509TrustManager x509ExtendedTrustManager) { - this.sslContext = checkNotNull(sslContext, "sslContext"); + this.sslContext = Preconditions.checkNotNull(sslContext, "sslContext"); this.x509ExtendedTrustManager = x509ExtendedTrustManager; } @@ -862,8 +858,8 @@ static final class Http2UpgradeAndGrpcHandler extends ChannelInboundHandlerAdapt private ProtocolNegotiationEvent pne; Http2UpgradeAndGrpcHandler(String authority, GrpcHttp2ConnectionHandler next) { - this.authority = checkNotNull(authority, "authority"); - this.next = checkNotNull(next, "next"); + this.authority = Preconditions.checkNotNull(authority, "authority"); + this.next = Preconditions.checkNotNull(next, "next"); this.negotiationLogger = next.getNegotiationLogger(); } @@ -907,9 +903,9 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc } /** - * Returns a {@link ChannelHandler} that ensures that the {@code handler} is added to the - * pipeline writes to the {@link Channel} may happen immediately, even before it - * is active. + * Returns a {@link io.netty.channel.ChannelHandler} that ensures that the {@code handler} is + * added to the pipeline writes to the {@link io.netty.channel.Channel} may happen immediately, + * even before it is active. */ public static ProtocolNegotiator plaintext() { return new PlaintextProtocolNegotiator(); @@ -987,7 +983,7 @@ static final class GrpcNegotiationHandler extends ChannelInboundHandlerAdapter { private final GrpcHttp2ConnectionHandler next; public GrpcNegotiationHandler(GrpcHttp2ConnectionHandler next) { - this.next = checkNotNull(next, "next"); + this.next = Preconditions.checkNotNull(next, "next"); } @Override @@ -1109,15 +1105,15 @@ static class ProtocolNegotiationHandler extends ChannelDuplexHandler { protected ProtocolNegotiationHandler(ChannelHandler next, String negotiatorName, ChannelLogger negotiationLogger) { - this.next = checkNotNull(next, "next"); + this.next = Preconditions.checkNotNull(next, "next"); this.negotiatorName = negotiatorName; - this.negotiationLogger = checkNotNull(negotiationLogger, "negotiationLogger"); + this.negotiationLogger = Preconditions.checkNotNull(negotiationLogger, "negotiationLogger"); } protected ProtocolNegotiationHandler(ChannelHandler next, ChannelLogger negotiationLogger) { - this.next = checkNotNull(next, "next"); + this.next = Preconditions.checkNotNull(next, "next"); this.negotiatorName = getClass().getSimpleName().replace("Handler", ""); - this.negotiationLogger = checkNotNull(negotiationLogger, "negotiationLogger"); + this.negotiationLogger = Preconditions.checkNotNull(negotiationLogger, "negotiationLogger"); } @Override @@ -1158,7 +1154,7 @@ protected final ProtocolNegotiationEvent getProtocolNegotiationEvent() { protected final void replaceProtocolNegotiationEvent(ProtocolNegotiationEvent pne) { checkState(this.pne != null, "previous protocol negotiation event hasn't triggered"); - this.pne = checkNotNull(pne); + this.pne = Preconditions.checkNotNull(pne); } protected final void fireProtocolNegotiationEvent(ChannelHandlerContext ctx) { diff --git a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java index b295fecdde8..d080bb78733 100644 --- a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java +++ b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java @@ -55,6 +55,7 @@ public X509AuthorityVerifier(SSLEngine sslEngine, X509TrustManager x509ExtendedT this.x509ExtendedTrustManager = x509ExtendedTrustManager; } + @Override public Status verifyAuthority(@Nonnull String authority) { // sslEngine won't be set when creating ClientTlsHandler from InternalProtocolNegotiators // for example. From 1ff1f0b9c3a5fcebe5f0d53a13054132442c7880 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Fri, 31 Jan 2025 14:33:43 +0000 Subject: [PATCH 66/70] Revert unintended changes. --- netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java index 6ba66fe4ff4..7959f0fdd9d 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java @@ -48,7 +48,7 @@ static GrpcHttp2OutboundHeaders clientRequestHeaders(byte[][] serializedMetadata String getAuthority() { for (int i = 0; i < preHeaders.length / 2; i++) { - if (preHeaders[i] == Http2Headers.PseudoHeaderName.AUTHORITY.value()) { + if (preHeaders[i].equals(Http2Headers.PseudoHeaderName.AUTHORITY.value())) { return preHeaders[i + 1].toString(); } } From 671dee06f9dbaafcc706b67b5621558d6d17d5bf Mon Sep 17 00:00:00 2001 From: deadEternally Date: Fri, 14 Feb 2025 21:22:22 +0530 Subject: [PATCH 67/70] Review comments. --- .../io/grpc/internal/CertificateUtils.java | 2 +- .../grpc/netty/GrpcHttp2OutboundHeaders.java | 5 +- .../io/grpc/netty/NettyClientHandler.java | 58 ++++--- .../io/grpc/netty/ProtocolNegotiators.java | 3 +- .../io/grpc/netty/X509AuthorityVerifier.java | 11 +- .../io/grpc/netty/NettyClientHandlerTest.java | 164 +++++++++++++++++- .../grpc/netty/NettyClientTransportTest.java | 2 +- 7 files changed, 209 insertions(+), 36 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/CertificateUtils.java b/core/src/main/java/io/grpc/internal/CertificateUtils.java index 7efd16eaf27..cc26cedb274 100644 --- a/core/src/main/java/io/grpc/internal/CertificateUtils.java +++ b/core/src/main/java/io/grpc/internal/CertificateUtils.java @@ -32,7 +32,7 @@ /** * Contains certificate/key PEM file utility method(s) for internal usage. */ -public class CertificateUtils { +public final class CertificateUtils { /** * Creates X509TrustManagers using the provided CA certs. */ diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java index 7959f0fdd9d..2f3e0bb38f6 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java @@ -46,13 +46,14 @@ static GrpcHttp2OutboundHeaders clientRequestHeaders(byte[][] serializedMetadata return new GrpcHttp2OutboundHeaders(preHeaders, serializedMetadata); } - String getAuthority() { + @Override + public String authority() { for (int i = 0; i < preHeaders.length / 2; i++) { if (preHeaders[i].equals(Http2Headers.PseudoHeaderName.AUTHORITY.value())) { return preHeaders[i + 1].toString(); } } - return null; + return ""; } static GrpcHttp2OutboundHeaders serverResponseHeaders(byte[][] serializedMetadata) { diff --git a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java index c4cf38b897b..ee5d47f436d 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java @@ -28,6 +28,7 @@ import io.grpc.Attributes; import io.grpc.ChannelLogger; import io.grpc.InternalChannelz; +import io.grpc.InternalStatus; import io.grpc.Metadata; import io.grpc.Status; import io.grpc.StatusException; @@ -586,17 +587,6 @@ protected boolean isGracefulShutdownComplete() { && ((StreamBufferingEncoder) encoder()).numBufferedStreams() == 0; } - private String getAuthorityPseudoHeader(Http2Headers http2Headers) { - if (http2Headers instanceof GrpcHttp2OutboundHeaders) { - return ((GrpcHttp2OutboundHeaders) http2Headers).getAuthority(); - } - try { - return http2Headers.authority().toString(); - } catch (UnsupportedOperationException e) { - return null; - } - } - /** * Attempts to create a new stream from the given command. If there are too many active streams, * the creation request is queued. @@ -613,28 +603,48 @@ private void createStream(CreateStreamCommand command, ChannelPromise promise) return; } - String authority = getAuthorityPseudoHeader(command.headers()); + CharSequence authority = command.headers().authority(); if (authority != null) { - Status authorityVerificationStatus = peerVerificationResults.get(authority); - if (authorityVerificationStatus == null && attributes != null - && attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) != null) { - authorityVerificationStatus = attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) - .verifyAuthority(((GrpcHttp2OutboundHeaders) command.headers()).getAuthority()); - peerVerificationResults.put(authority, authorityVerificationStatus); + Status authorityVerificationStatus = peerVerificationResults.get(authority.toString()); + if (authorityVerificationStatus == null) { + if (attributes != null + && attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) != null) { + authorityVerificationStatus = attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) + .verifyAuthority(authority.toString()); + peerVerificationResults.put(authority.toString(), authorityVerificationStatus); + if (!authorityVerificationStatus.isOk() && !enablePerRpcAuthorityCheck) { + logger.log(Level.WARNING, String.format("%s.%s", + authorityVerificationStatus.getDescription(), enablePerRpcAuthorityCheck + ? "" : " This will be an error in the future."), + InternalStatus.asRuntimeExceptionWithoutStacktrace(authorityVerificationStatus, null)); + } + } else { + authorityVerificationStatus = Status.UNAVAILABLE.withDescription( + "Authority verifier not found to verify authority"); + command.stream().setNonExistent(); + command.stream().transportReportStatus( + authorityVerificationStatus, RpcProgress.DROPPED, true, new Metadata()); + promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace(authorityVerificationStatus, null)); + return; + } } if (authorityVerificationStatus != null && !authorityVerificationStatus.isOk()) { - logger.log(Level.WARNING, String.format("%s.%s", - authorityVerificationStatus.getDescription(), enablePerRpcAuthorityCheck - ? "" : "This will be an error in the future."), - authorityVerificationStatus.getCause()); if (enablePerRpcAuthorityCheck) { command.stream().setNonExistent(); command.stream().transportReportStatus( - authorityVerificationStatus, RpcProgress.DROPPED, true, new Metadata()); - promise.setFailure(authorityVerificationStatus.getCause()); + authorityVerificationStatus, RpcProgress.DROPPED, true, new Metadata()); + promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace(authorityVerificationStatus, null)); return; } } + } else { + Status authorityVerificationStatus = Status.UNAVAILABLE.withDescription( + "Missing authority header"); + command.stream().setNonExistent(); + command.stream().transportReportStatus( + Status.UNAVAILABLE, RpcProgress.DROPPED, true, new Metadata()); + promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace(authorityVerificationStatus, null)); + return; } // Get the stream ID for the new stream. int streamId; diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 2def192c36f..33a8e6e1af8 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -762,8 +762,7 @@ public static ProtocolNegotiator tls(SslContext sslContext, */ public static ProtocolNegotiator tls(SslContext sslContext, X509TrustManager x509ExtendedTrustManager) { - return tls(sslContext, null, Optional.absent(), - x509ExtendedTrustManager); + return tls(sslContext, null, Optional.absent(), x509ExtendedTrustManager); } public static ProtocolNegotiator.ClientFactory tlsClientFactory(SslContext sslContext, diff --git a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java index d080bb78733..03842016860 100644 --- a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java +++ b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java @@ -28,7 +28,7 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.X509TrustManager; -public class X509AuthorityVerifier implements AuthorityVerifier { +final class X509AuthorityVerifier implements AuthorityVerifier { private final SSLEngine sslEngine; private final X509TrustManager x509ExtendedTrustManager; @@ -57,17 +57,15 @@ public X509AuthorityVerifier(SSLEngine sslEngine, X509TrustManager x509ExtendedT @Override public Status verifyAuthority(@Nonnull String authority) { - // sslEngine won't be set when creating ClientTlsHandler from InternalProtocolNegotiators - // for example. if (sslEngine == null || x509ExtendedTrustManager == null) { - return Status.FAILED_PRECONDITION.withDescription( + return Status.UNAVAILABLE.withDescription( "Can't allow authority override in rpc when SslEngine or X509ExtendedTrustManager" + " is not available"); } Status peerVerificationStatus; try { // Because the authority pseudo-header can contain a port number: - // https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2.3com + // https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2.3 verifyAuthorityAllowedForPeerCert(removeAnyPortNumber(authority)); peerVerificationStatus = Status.OK; } catch (SSLPeerUnverifiedException | CertificateException | InvocationTargetException @@ -99,6 +97,9 @@ private void verifyAuthorityAllowedForPeerCert(String authority) for (int i = 0; i < peerCertificates.length; i++) { x509PeerCertificates[i] = (X509Certificate) peerCertificates[i]; } + if (checkServerTrustedMethod == null) { + throw new IllegalStateException("checkServerTrustedMethod not found"); + } checkServerTrustedMethod.invoke( x509ExtendedTrustManager, x509PeerCertificates, "RSA", sslEngineWrapper); } diff --git a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java index e5c97e9efd9..7de3914a6f8 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java @@ -36,6 +36,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; @@ -64,6 +65,7 @@ import io.grpc.internal.ClientStreamListener.RpcProgress; import io.grpc.internal.ClientTransport; import io.grpc.internal.ClientTransport.PingCallback; +import io.grpc.internal.GrpcAttributes; import io.grpc.internal.GrpcUtil; import io.grpc.internal.KeepAliveManager; import io.grpc.internal.ManagedClientTransport; @@ -90,10 +92,12 @@ import io.netty.handler.codec.http2.Http2Stream; import io.netty.util.AsciiString; import java.io.InputStream; +import java.security.cert.CertificateException; import java.text.MessageFormat; import java.util.LinkedList; import java.util.List; import java.util.Queue; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Handler; @@ -108,6 +112,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -189,7 +194,11 @@ public Void answer(InvocationOnMock invocation) throws Throwable { }) .when(streamListener) .messagesAvailable(ArgumentMatchers.any()); - + doAnswer((attributes) -> Attributes.newBuilder().set( + GrpcAttributes.ATTR_AUTHORITY_VERIFIER, + (authority) -> Status.OK).build()) + .when(listener) + .filterTransport(ArgumentMatchers.any(Attributes.class)); lifecycleManager = new ClientTransportLifecycleManager(listener); // This mocks the keepalive manager only for there's in which we verify it. For other tests // it'll be null which will be testing if we behave correctly when it's not present. @@ -919,6 +928,159 @@ public void exceptionCaughtShouldCloseConnection() throws Exception { assertFalse(channel().isOpen()); } + @Test + public void missingAuthorityHeader_streamCreationShouldFail() throws Exception { + Http2Headers grpcHeadersWithoutAuthority = new DefaultHttp2Headers() + .scheme(HTTPS) + .path(as("/fakemethod")) + .method(HTTP_METHOD) + .add(as("auth"), as("sometoken")) + .add(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC) + .add(TE_HEADER, TE_TRAILERS); + ChannelFuture channelFuture = enqueue(newCreateStreamCommand(grpcHeadersWithoutAuthority, streamTransportState)); + try { + channelFuture.get(); + fail("Expected stream creation failure"); + } catch (ExecutionException e) { + assertThat(e.getCause().getMessage()).isEqualTo("UNAVAILABLE: Missing authority header"); + } + } + + @Test + public void missingAuthorityVerifierInAttributes_streamCreationShouldFail() throws Exception { + doAnswer( + new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + StreamListener.MessageProducer producer = + (StreamListener.MessageProducer) invocation.getArguments()[0]; + InputStream message; + while ((message = producer.next()) != null) { + streamListenerMessageQueue.add(message); + } + return null; + } + }) + .when(streamListener) + .messagesAvailable(ArgumentMatchers.any()); + doAnswer((attributes) -> Attributes.EMPTY) + .when(listener) + .filterTransport(ArgumentMatchers.any(Attributes.class)); + lifecycleManager = new ClientTransportLifecycleManager(listener); + // This mocks the keepalive manager only for there's in which we verify it. For other tests + // it'll be null which will be testing if we behave correctly when it's not present. + if (setKeepaliveManagerFor.contains(testNameRule.getMethodName())) { + mockKeepAliveManager = mock(KeepAliveManager.class); + } + + initChannel(new GrpcHttp2ClientHeadersDecoder(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE)); + streamTransportState = new TransportStateImpl( + handler(), + channel().eventLoop(), + DEFAULT_MAX_MESSAGE_SIZE, + transportTracer); + streamTransportState.setListener(streamListener); + + grpcHeaders = new DefaultHttp2Headers() + .scheme(HTTPS) + .authority(as("www.fake.com")) + .path(as("/fakemethod")) + .method(HTTP_METHOD) + .add(as("auth"), as("sometoken")) + .add(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC) + .add(TE_HEADER, TE_TRAILERS); + + // Simulate receipt of initial remote settings. + ByteBuf serializedSettings = serializeSettings(new Http2Settings()); + channelRead(serializedSettings); + channel().releaseOutbound(); + + ChannelFuture channelFuture = createStream(); + try { + channelFuture.get(); + fail("Expected stream creation failure"); + } catch (ExecutionException e) { + assertThat(e.getCause().getMessage()).isEqualTo("UNAVAILABLE: Authority verifier not found to verify authority"); + } + } + + @Test + public void authorityVerificationSuccess_streamCreationSucceeds() throws Exception { + NettyClientHandler.enablePerRpcAuthorityCheck = true; + try { + ChannelFuture channelFuture = createStream(); + channelFuture.get(); + } finally { + NettyClientHandler.enablePerRpcAuthorityCheck = false; + } + } + + @Test + public void authorityVerificationFailure_streamCreationFails() throws Exception { + NettyClientHandler.enablePerRpcAuthorityCheck = true; + try { + doAnswer( + new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + StreamListener.MessageProducer producer = + (StreamListener.MessageProducer) invocation.getArguments()[0]; + InputStream message; + while ((message = producer.next()) != null) { + streamListenerMessageQueue.add(message); + } + return null; + } + }) + .when(streamListener) + .messagesAvailable(ArgumentMatchers.any()); + doAnswer((attributes) -> Attributes.newBuilder().set( + GrpcAttributes.ATTR_AUTHORITY_VERIFIER, + (authority) -> Status.UNAVAILABLE.withCause( + new CertificateException("Peer verification failed"))).build()) + .when(listener) + .filterTransport(ArgumentMatchers.any(Attributes.class)); + lifecycleManager = new ClientTransportLifecycleManager(listener); + // This mocks the keepalive manager only for there's in which we verify it. For other tests + // it'll be null which will be testing if we behave correctly when it's not present. + if (setKeepaliveManagerFor.contains(testNameRule.getMethodName())) { + mockKeepAliveManager = mock(KeepAliveManager.class); + } + + initChannel(new GrpcHttp2ClientHeadersDecoder(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE)); + streamTransportState = new TransportStateImpl( + handler(), + channel().eventLoop(), + DEFAULT_MAX_MESSAGE_SIZE, + transportTracer); + streamTransportState.setListener(streamListener); + + grpcHeaders = new DefaultHttp2Headers() + .scheme(HTTPS) + .authority(as("www.fake.com")) + .path(as("/fakemethod")) + .method(HTTP_METHOD) + .add(as("auth"), as("sometoken")) + .add(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC) + .add(TE_HEADER, TE_TRAILERS); + + // Simulate receipt of initial remote settings. + ByteBuf serializedSettings = serializeSettings(new Http2Settings()); + channelRead(serializedSettings); + channel().releaseOutbound(); + + ChannelFuture channelFuture = createStream(); + try { + channelFuture.get(); + fail("Expected stream creation failure"); + } catch (ExecutionException e) { + assertThat(e.getMessage()).isEqualTo("io.grpc.InternalStatusRuntimeException: UNAVAILABLE"); + } + } finally { + NettyClientHandler.enablePerRpcAuthorityCheck = false; + } + } + @Override protected void makeStream() throws Exception { createStream(); diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 1e52cd1f592..488b2f381e7 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -888,7 +888,7 @@ public void authorityOverrideInCallOptions_noX509ExtendedTrustManager_newStreamC Status status = ((StatusException) ex.getCause()).getStatus(); assertThat(status.getDescription()).isEqualTo("Can't allow authority override in rpc " + "when SslEngine or X509ExtendedTrustManager is not available"); - assertThat(status.getCode()).isEqualTo(Code.FAILED_PRECONDITION); + assertThat(status.getCode()).isEqualTo(Code.UNAVAILABLE); } } finally { NettyClientHandler.enablePerRpcAuthorityCheck = false; From 671444b72a1af4a513a67b2601655a97655f3010 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Wed, 19 Feb 2025 18:45:24 +0530 Subject: [PATCH 68/70] Review comments --- .../grpc/netty/GrpcHttp2OutboundHeaders.java | 22 +++--- .../io/grpc/netty/NettyClientHandler.java | 71 +++++++++-------- .../io/grpc/netty/NettyClientHandlerTest.java | 79 +++++++++---------- 3 files changed, 91 insertions(+), 81 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java index 2f3e0bb38f6..ebb15d1f52d 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java @@ -46,16 +46,6 @@ static GrpcHttp2OutboundHeaders clientRequestHeaders(byte[][] serializedMetadata return new GrpcHttp2OutboundHeaders(preHeaders, serializedMetadata); } - @Override - public String authority() { - for (int i = 0; i < preHeaders.length / 2; i++) { - if (preHeaders[i].equals(Http2Headers.PseudoHeaderName.AUTHORITY.value())) { - return preHeaders[i + 1].toString(); - } - } - return ""; - } - static GrpcHttp2OutboundHeaders serverResponseHeaders(byte[][] serializedMetadata) { AsciiString[] preHeaders = new AsciiString[] { Http2Headers.PseudoHeaderName.STATUS.value(), Utils.STATUS_OK, @@ -76,6 +66,18 @@ private GrpcHttp2OutboundHeaders(AsciiString[] preHeaders, byte[][] serializedMe this.preHeaders = preHeaders; } + @Override + public CharSequence authority() { + CharSequence authority = null; + for (int i = 0; i < preHeaders.length / 2; i++) { + if (preHeaders[i].equals(Http2Headers.PseudoHeaderName.AUTHORITY.value())) { + authority = preHeaders[i + 1]; + } + } + assert authority != null; + return authority; + } + @Override @SuppressWarnings("ReferenceEquality") // STATUS.value() never changes. public CharSequence status() { diff --git a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java index ee5d47f436d..822d8164e93 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java @@ -603,38 +603,46 @@ private void createStream(CreateStreamCommand command, ChannelPromise promise) return; } - CharSequence authority = command.headers().authority(); - if (authority != null) { - Status authorityVerificationStatus = peerVerificationResults.get(authority.toString()); - if (authorityVerificationStatus == null) { - if (attributes != null - && attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) != null) { - authorityVerificationStatus = attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) - .verifyAuthority(authority.toString()); - peerVerificationResults.put(authority.toString(), authorityVerificationStatus); - if (!authorityVerificationStatus.isOk() && !enablePerRpcAuthorityCheck) { - logger.log(Level.WARNING, String.format("%s.%s", - authorityVerificationStatus.getDescription(), enablePerRpcAuthorityCheck - ? "" : " This will be an error in the future."), - InternalStatus.asRuntimeExceptionWithoutStacktrace(authorityVerificationStatus, null)); + CharSequence authorityHeader = command.headers().authority(); + if (authorityHeader != null) { + // No need to verify authority for the rpc outgoing header if it is same as the authority + // for the transport + if (!authority.contentEquals(authorityHeader)) { + Status authorityVerificationStatus = peerVerificationResults.get( + authorityHeader.toString()); + if (authorityVerificationStatus == null) { + if (attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) != null) { + authorityVerificationStatus = attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) + .verifyAuthority(authorityHeader.toString()); + peerVerificationResults.put(authorityHeader.toString(), authorityVerificationStatus); + if (!authorityVerificationStatus.isOk() && !enablePerRpcAuthorityCheck) { + logger.log(Level.WARNING, String.format("%s.%s", + authorityVerificationStatus.getDescription(), + enablePerRpcAuthorityCheck + ? "" : " This will be an error in the future."), + InternalStatus.asRuntimeExceptionWithoutStacktrace( + authorityVerificationStatus, null)); + } + } else { + authorityVerificationStatus = Status.UNAVAILABLE.withDescription( + "Authority verifier not found to verify authority"); + command.stream().setNonExistent(); + command.stream().transportReportStatus( + authorityVerificationStatus, RpcProgress.PROCESSED, true, new Metadata()); + promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace( + authorityVerificationStatus, null)); + return; } - } else { - authorityVerificationStatus = Status.UNAVAILABLE.withDescription( - "Authority verifier not found to verify authority"); - command.stream().setNonExistent(); - command.stream().transportReportStatus( - authorityVerificationStatus, RpcProgress.DROPPED, true, new Metadata()); - promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace(authorityVerificationStatus, null)); - return; } - } - if (authorityVerificationStatus != null && !authorityVerificationStatus.isOk()) { - if (enablePerRpcAuthorityCheck) { - command.stream().setNonExistent(); - command.stream().transportReportStatus( - authorityVerificationStatus, RpcProgress.DROPPED, true, new Metadata()); - promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace(authorityVerificationStatus, null)); - return; + if (authorityVerificationStatus != null && !authorityVerificationStatus.isOk()) { + if (enablePerRpcAuthorityCheck) { + command.stream().setNonExistent(); + command.stream().transportReportStatus( + authorityVerificationStatus, RpcProgress.PROCESSED, true, new Metadata()); + promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace( + authorityVerificationStatus, null)); + return; + } } } } else { @@ -643,7 +651,8 @@ private void createStream(CreateStreamCommand command, ChannelPromise promise) command.stream().setNonExistent(); command.stream().transportReportStatus( Status.UNAVAILABLE, RpcProgress.DROPPED, true, new Metadata()); - promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace(authorityVerificationStatus, null)); + promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace( + authorityVerificationStatus, null)); return; } // Get the stream ID for the new stream. diff --git a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java index 7de3914a6f8..945c6c1267a 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java @@ -112,7 +112,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -937,7 +936,8 @@ public void missingAuthorityHeader_streamCreationShouldFail() throws Exception { .add(as("auth"), as("sometoken")) .add(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC) .add(TE_HEADER, TE_TRAILERS); - ChannelFuture channelFuture = enqueue(newCreateStreamCommand(grpcHeadersWithoutAuthority, streamTransportState)); + ChannelFuture channelFuture = enqueue(newCreateStreamCommand( + grpcHeadersWithoutAuthority, streamTransportState)); try { channelFuture.get(); fail("Expected stream creation failure"); @@ -948,24 +948,23 @@ public void missingAuthorityHeader_streamCreationShouldFail() throws Exception { @Test public void missingAuthorityVerifierInAttributes_streamCreationShouldFail() throws Exception { - doAnswer( - new Answer() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - StreamListener.MessageProducer producer = - (StreamListener.MessageProducer) invocation.getArguments()[0]; - InputStream message; - while ((message = producer.next()) != null) { - streamListenerMessageQueue.add(message); - } - return null; - } - }) - .when(streamListener) - .messagesAvailable(ArgumentMatchers.any()); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + StreamListener.MessageProducer producer = + (StreamListener.MessageProducer) invocation.getArguments()[0]; + InputStream message; + while ((message = producer.next()) != null) { + streamListenerMessageQueue.add(message); + } + return null; + } + }) + .when(streamListener) + .messagesAvailable(ArgumentMatchers.any()); doAnswer((attributes) -> Attributes.EMPTY) - .when(listener) - .filterTransport(ArgumentMatchers.any(Attributes.class)); + .when(listener) + .filterTransport(ArgumentMatchers.any(Attributes.class)); lifecycleManager = new ClientTransportLifecycleManager(listener); // This mocks the keepalive manager only for there's in which we verify it. For other tests // it'll be null which will be testing if we behave correctly when it's not present. @@ -1000,7 +999,8 @@ public Void answer(InvocationOnMock invocation) throws Throwable { channelFuture.get(); fail("Expected stream creation failure"); } catch (ExecutionException e) { - assertThat(e.getCause().getMessage()).isEqualTo("UNAVAILABLE: Authority verifier not found to verify authority"); + assertThat(e.getCause().getMessage()).isEqualTo( + "UNAVAILABLE: Authority verifier not found to verify authority"); } } @@ -1019,27 +1019,26 @@ public void authorityVerificationSuccess_streamCreationSucceeds() throws Excepti public void authorityVerificationFailure_streamCreationFails() throws Exception { NettyClientHandler.enablePerRpcAuthorityCheck = true; try { - doAnswer( - new Answer() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - StreamListener.MessageProducer producer = - (StreamListener.MessageProducer) invocation.getArguments()[0]; - InputStream message; - while ((message = producer.next()) != null) { - streamListenerMessageQueue.add(message); - } - return null; - } - }) - .when(streamListener) - .messagesAvailable(ArgumentMatchers.any()); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + StreamListener.MessageProducer producer = + (StreamListener.MessageProducer) invocation.getArguments()[0]; + InputStream message; + while ((message = producer.next()) != null) { + streamListenerMessageQueue.add(message); + } + return null; + } + }) + .when(streamListener) + .messagesAvailable(ArgumentMatchers.any()); doAnswer((attributes) -> Attributes.newBuilder().set( - GrpcAttributes.ATTR_AUTHORITY_VERIFIER, - (authority) -> Status.UNAVAILABLE.withCause( - new CertificateException("Peer verification failed"))).build()) - .when(listener) - .filterTransport(ArgumentMatchers.any(Attributes.class)); + GrpcAttributes.ATTR_AUTHORITY_VERIFIER, + (authority) -> Status.UNAVAILABLE.withCause( + new CertificateException("Peer verification failed"))).build()) + .when(listener) + .filterTransport(ArgumentMatchers.any(Attributes.class)); lifecycleManager = new ClientTransportLifecycleManager(listener); // This mocks the keepalive manager only for there's in which we verify it. For other tests // it'll be null which will be testing if we behave correctly when it's not present. From 7ecbf56e267ecd02d48498e462ba392d77c25503 Mon Sep 17 00:00:00 2001 From: deadEternally Date: Thu, 20 Feb 2025 21:15:51 +0530 Subject: [PATCH 69/70] Fix review comments --- .../grpc/netty/GrpcHttp2OutboundHeaders.java | 6 +- .../io/grpc/netty/NettyClientHandler.java | 84 +++++++++---------- .../io/grpc/netty/ProtocolNegotiators.java | 24 +++++- .../io/grpc/netty/X509AuthorityVerifier.java | 8 +- 4 files changed, 70 insertions(+), 52 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java index ebb15d1f52d..6253656e3ee 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java @@ -68,14 +68,12 @@ private GrpcHttp2OutboundHeaders(AsciiString[] preHeaders, byte[][] serializedMe @Override public CharSequence authority() { - CharSequence authority = null; for (int i = 0; i < preHeaders.length / 2; i++) { if (preHeaders[i].equals(Http2Headers.PseudoHeaderName.AUTHORITY.value())) { - authority = preHeaders[i + 1]; + return preHeaders[i * 2 + 1]; } } - assert authority != null; - return authority; + return null; } @Override diff --git a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java index 822d8164e93..5a93dbc982b 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java @@ -604,57 +604,55 @@ private void createStream(CreateStreamCommand command, ChannelPromise promise) } CharSequence authorityHeader = command.headers().authority(); - if (authorityHeader != null) { - // No need to verify authority for the rpc outgoing header if it is same as the authority - // for the transport - if (!authority.contentEquals(authorityHeader)) { - Status authorityVerificationStatus = peerVerificationResults.get( - authorityHeader.toString()); - if (authorityVerificationStatus == null) { - if (attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) != null) { - authorityVerificationStatus = attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) - .verifyAuthority(authorityHeader.toString()); - peerVerificationResults.put(authorityHeader.toString(), authorityVerificationStatus); - if (!authorityVerificationStatus.isOk() && !enablePerRpcAuthorityCheck) { - logger.log(Level.WARNING, String.format("%s.%s", - authorityVerificationStatus.getDescription(), - enablePerRpcAuthorityCheck - ? "" : " This will be an error in the future."), - InternalStatus.asRuntimeExceptionWithoutStacktrace( - authorityVerificationStatus, null)); - } - } else { - authorityVerificationStatus = Status.UNAVAILABLE.withDescription( - "Authority verifier not found to verify authority"); - command.stream().setNonExistent(); - command.stream().transportReportStatus( - authorityVerificationStatus, RpcProgress.PROCESSED, true, new Metadata()); - promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace( - authorityVerificationStatus, null)); - return; - } - } - if (authorityVerificationStatus != null && !authorityVerificationStatus.isOk()) { - if (enablePerRpcAuthorityCheck) { - command.stream().setNonExistent(); - command.stream().transportReportStatus( - authorityVerificationStatus, RpcProgress.PROCESSED, true, new Metadata()); - promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace( - authorityVerificationStatus, null)); - return; - } - } - } - } else { + if (authorityHeader == null) { Status authorityVerificationStatus = Status.UNAVAILABLE.withDescription( "Missing authority header"); command.stream().setNonExistent(); command.stream().transportReportStatus( - Status.UNAVAILABLE, RpcProgress.DROPPED, true, new Metadata()); + Status.UNAVAILABLE, RpcProgress.PROCESSED, true, new Metadata()); promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace( authorityVerificationStatus, null)); return; } + // No need to verify authority for the rpc outgoing header if it is same as the authority + // for the transport + if (!authority.contentEquals(authorityHeader)) { + Status authorityVerificationStatus = peerVerificationResults.get( + authorityHeader.toString()); + if (authorityVerificationStatus == null) { + if (attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) == null) { + authorityVerificationStatus = Status.UNAVAILABLE.withDescription( + "Authority verifier not found to verify authority"); + command.stream().setNonExistent(); + command.stream().transportReportStatus( + authorityVerificationStatus, RpcProgress.PROCESSED, true, new Metadata()); + promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace( + authorityVerificationStatus, null)); + return; + } + authorityVerificationStatus = attributes.get(GrpcAttributes.ATTR_AUTHORITY_VERIFIER) + .verifyAuthority(authorityHeader.toString()); + peerVerificationResults.put(authorityHeader.toString(), authorityVerificationStatus); + if (!authorityVerificationStatus.isOk() && !enablePerRpcAuthorityCheck) { + logger.log(Level.WARNING, String.format("%s.%s", + authorityVerificationStatus.getDescription(), + enablePerRpcAuthorityCheck + ? "" : " This will be an error in the future."), + InternalStatus.asRuntimeExceptionWithoutStacktrace( + authorityVerificationStatus, null)); + } + } + if (!authorityVerificationStatus.isOk()) { + if (enablePerRpcAuthorityCheck) { + command.stream().setNonExistent(); + command.stream().transportReportStatus( + authorityVerificationStatus, RpcProgress.PROCESSED, true, new Metadata()); + promise.setFailure(InternalStatus.asRuntimeExceptionWithoutStacktrace( + authorityVerificationStatus, null)); + return; + } + } + } // Get the stream ID for the new stream. int streamId; try { diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 33a8e6e1af8..77308c76ace 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -836,7 +836,9 @@ public AsciiString scheme() { public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) { ChannelHandler upgradeHandler = new Http2UpgradeAndGrpcHandler(grpcHandler.getAuthority(), grpcHandler); - return new WaitUntilActiveHandler(upgradeHandler, grpcHandler.getNegotiationLogger()); + ChannelHandler plaintextHandler = + new PlaintextHandler(upgradeHandler, grpcHandler.getNegotiationLogger()); + return new WaitUntilActiveHandler(plaintextHandler, grpcHandler.getNegotiationLogger()); } @Override @@ -1033,7 +1035,9 @@ static final class PlaintextProtocolNegotiator implements ProtocolNegotiator { @Override public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) { ChannelHandler grpcNegotiationHandler = new GrpcNegotiationHandler(grpcHandler); - ChannelHandler activeHandler = new WaitUntilActiveHandler(grpcNegotiationHandler, + ChannelHandler plaintextHandler = + new PlaintextHandler(grpcNegotiationHandler, grpcHandler.getNegotiationLogger()); + ChannelHandler activeHandler = new WaitUntilActiveHandler(plaintextHandler, grpcHandler.getNegotiationLogger()); return activeHandler; } @@ -1047,6 +1051,22 @@ public AsciiString scheme() { } } + static final class PlaintextHandler extends ProtocolNegotiationHandler { + PlaintextHandler(ChannelHandler next, ChannelLogger negotiationLogger) { + super(next, negotiationLogger); + } + + @Override + protected void protocolNegotiationEventTriggered(ChannelHandlerContext ctx) { + ProtocolNegotiationEvent existingPne = getProtocolNegotiationEvent(); + Attributes attrs = existingPne.getAttributes().toBuilder() + .set(GrpcAttributes.ATTR_AUTHORITY_VERIFIER, (authority) -> Status.OK) + .build(); + replaceProtocolNegotiationEvent(existingPne.withAttributes(attrs)); + fireProtocolNegotiationEvent(ctx); + } + } + /** * Waits for the channel to be active, and then installs the next Handler. Using this allows * subsequent handlers to assume the channel is active and ready to send. Additionally, this a diff --git a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java index 03842016860..8771d333b31 100644 --- a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java +++ b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java @@ -16,6 +16,8 @@ package io.grpc.netty; +import static com.google.common.base.Preconditions.checkNotNull; + import io.grpc.Status; import io.grpc.internal.AuthorityVerifier; import java.lang.reflect.InvocationTargetException; @@ -51,15 +53,15 @@ final class X509AuthorityVerifier implements AuthorityVerifier { } public X509AuthorityVerifier(SSLEngine sslEngine, X509TrustManager x509ExtendedTrustManager) { - this.sslEngine = sslEngine; + this.sslEngine = checkNotNull(sslEngine); this.x509ExtendedTrustManager = x509ExtendedTrustManager; } @Override public Status verifyAuthority(@Nonnull String authority) { - if (sslEngine == null || x509ExtendedTrustManager == null) { + if (x509ExtendedTrustManager == null) { return Status.UNAVAILABLE.withDescription( - "Can't allow authority override in rpc when SslEngine or X509ExtendedTrustManager" + "Can't allow authority override in rpc when X509ExtendedTrustManager" + " is not available"); } Status peerVerificationStatus; From e7c791369a08c747d06699c85ae6373ea47d5a9e Mon Sep 17 00:00:00 2001 From: deadEternally Date: Fri, 21 Feb 2025 15:24:23 +0530 Subject: [PATCH 70/70] Fix test and some mistakes. --- netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java | 2 +- netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java | 2 +- netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java index 6253656e3ee..aabcd4fbaaa 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java @@ -69,7 +69,7 @@ private GrpcHttp2OutboundHeaders(AsciiString[] preHeaders, byte[][] serializedMe @Override public CharSequence authority() { for (int i = 0; i < preHeaders.length / 2; i++) { - if (preHeaders[i].equals(Http2Headers.PseudoHeaderName.AUTHORITY.value())) { + if (preHeaders[i * 2].equals(Http2Headers.PseudoHeaderName.AUTHORITY.value())) { return preHeaders[i * 2 + 1]; } } diff --git a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java index 8771d333b31..8a8d426662f 100644 --- a/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java +++ b/netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java @@ -53,7 +53,7 @@ final class X509AuthorityVerifier implements AuthorityVerifier { } public X509AuthorityVerifier(SSLEngine sslEngine, X509TrustManager x509ExtendedTrustManager) { - this.sslEngine = checkNotNull(sslEngine); + this.sslEngine = checkNotNull(sslEngine, "sslEngine"); this.x509ExtendedTrustManager = x509ExtendedTrustManager; } diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 488b2f381e7..9b3b2e386d3 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -887,7 +887,7 @@ public void authorityOverrideInCallOptions_noX509ExtendedTrustManager_newStreamC } catch (ExecutionException ex) { Status status = ((StatusException) ex.getCause()).getStatus(); assertThat(status.getDescription()).isEqualTo("Can't allow authority override in rpc " - + "when SslEngine or X509ExtendedTrustManager is not available"); + + "when X509ExtendedTrustManager is not available"); assertThat(status.getCode()).isEqualTo(Code.UNAVAILABLE); } } finally {