Skip to content

Commit 32104ce

Browse files
committed
Address review comments.
1 parent 1e072d4 commit 32104ce

File tree

6 files changed

+109
-50
lines changed

6 files changed

+109
-50
lines changed

core/src/main/java/io/grpc/internal/CertificateUtils.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright 2024 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package io.grpc.internal;
218

319
import java.io.IOException;
@@ -16,6 +32,9 @@
1632
import javax.net.ssl.X509ExtendedTrustManager;
1733
import javax.security.auth.x500.X500Principal;
1834

35+
/**
36+
* Contains certificate/key PEM file utility method(s) for internal usage.
37+
*/
1938
public class CertificateUtils {
2039
/**
2140
* Creates a X509ExtendedTrustManager using the provided CA certs if applicable for the

netty/src/main/java/io/grpc/netty/NettyClientTransport.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
import io.grpc.internal.StatsTraceContext;
4646
import io.grpc.internal.TransportTracer;
4747
import io.grpc.netty.NettyChannelBuilder.LocalSocketPicker;
48-
import io.grpc.netty.ProtocolNegotiators.ClientTlsProtocolNegotiator;
4948
import io.netty.bootstrap.Bootstrap;
5049
import io.netty.channel.Channel;
5150
import io.netty.channel.ChannelFactory;
@@ -63,7 +62,6 @@
6362
import java.nio.channels.ClosedChannelException;
6463
import java.security.cert.CertificateException;
6564
import java.util.Map;
66-
import java.util.concurrent.ConcurrentHashMap;
6765
import java.util.concurrent.Executor;
6866
import java.util.concurrent.TimeUnit;
6967
import java.util.logging.Level;
@@ -112,8 +110,6 @@ class NettyClientTransport implements ConnectionClientTransport {
112110
private final ChannelLogger channelLogger;
113111
private final boolean useGetForSafeMethods;
114112
private final Ticker ticker;
115-
private final ConcurrentHashMap<String, Boolean> authoritiesAllowedForPeer =
116-
new ConcurrentHashMap<>();
117113

118114
NettyClientTransport(
119115
SocketAddress address,
@@ -206,22 +202,23 @@ public ClientStream newStream(
206202
try {
207203
if (callOptions.getAuthority() != null && !negotiator.mayBeVerifyAuthority(
208204
callOptions.getAuthority())) {
209-
logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.",
205+
String errMsg = String.format("Peer hostname verification failed for authority '%s'.",
210206
callOptions.getAuthority());
211-
return new FailingClientStream(Status.INTERNAL.withDescription(
212-
"Peer hostname verification failed for authority"), tracers);
207+
logger.log(Level.FINE, errMsg);
208+
return new FailingClientStream(Status.INTERNAL.withDescription(errMsg), tracers);
213209
}
214210
} catch (UnsupportedOperationException ex) {
215-
logger.log(Level.FINE, "Can't allow authority override in rpc when X509ExtendedTrustManager is not available.",
211+
logger.log(Level.FINE,
212+
"Can't allow authority override in rpc when X509ExtendedTrustManager is not available.",
216213
callOptions.getAuthority());
217214
return new FailingClientStream(Status.INTERNAL.withDescription(
218215
"Can't allow authority override in rpc when X509ExtendedTrustManager is not available"),
219216
tracers);
220217
} catch (SSLPeerUnverifiedException | CertificateException ex) {
221-
logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.",
218+
String errMsg = String.format("Peer hostname verification failed for authority '%s'.",
222219
callOptions.getAuthority());
223-
return new FailingClientStream(Status.INTERNAL.withDescription(
224-
"Peer hostname verification failed for authority"), tracers);
220+
logger.log(Level.FINE, errMsg, ex);
221+
return new FailingClientStream(Status.INTERNAL.withDescription(errMsg), tracers);
225222
}
226223
StatsTraceContext statsTraceCtx =
227224
StatsTraceContext.newClientContext(tracers, getAttributes(), headers);

netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,12 @@ interface ServerFactory {
6969
/**
7070
* Verify the authority against peer if applicable depending on the transport credential type.
7171
* @throws UnsupportedOperationException if the verification should happen but the required
72-
* type of TrustManager could not be found.
72+
* type of TrustManager could not be found.
7373
* @throws SSLPeerUnverifiedException if peer verification failed
7474
* @throws CertificateException if certificates have a problem
7575
*/
76-
default boolean mayBeVerifyAuthority(String authority) throws SSLPeerUnverifiedException, CertificateException {
76+
default boolean mayBeVerifyAuthority(String authority)
77+
throws SSLPeerUnverifiedException, CertificateException {
7778
return true;
7879
}
7980
}

netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import io.grpc.Status;
4343
import io.grpc.TlsChannelCredentials;
4444
import io.grpc.TlsServerCredentials;
45-
import io.grpc.internal.FailingClientStream;
4645
import io.grpc.internal.GrpcAttributes;
4746
import io.grpc.internal.GrpcUtil;
4847
import io.grpc.internal.ObjectPool;
@@ -81,6 +80,7 @@
8180
import java.security.cert.X509Certificate;
8281
import java.util.Arrays;
8382
import java.util.EnumSet;
83+
import java.util.LinkedHashMap;
8484
import java.util.Optional;
8585
import java.util.Set;
8686
import java.util.concurrent.Executor;
@@ -574,6 +574,8 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws
574574

575575
static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator {
576576

577+
private static final int MAX_AUTHORITIES_CACHE_SIZE = 100;
578+
private final LinkedHashMap<String, Boolean> authoritiesAllowedForPeer = new LinkedHashMap<>();
577579
private SSLEngine sslEngine;
578580

579581
public ClientTlsProtocolNegotiator(SslContext sslContext,
@@ -615,13 +617,43 @@ public void close() {
615617
}
616618
}
617619

620+
@Override
621+
synchronized public boolean mayBeVerifyAuthority(@Nonnull String authority) {
622+
if (!canVerifyAuthorityOverride()) {
623+
throw new UnsupportedOperationException(
624+
"Can't allow authority override in rpc when X509ExtendedTrustManager is not"
625+
+ "available.");
626+
}
627+
boolean peerVerified;
628+
if (authoritiesAllowedForPeer.containsKey(authority)) {
629+
peerVerified = authoritiesAllowedForPeer.get(authority);
630+
} else {
631+
try {
632+
verifyAuthorityAllowedForPeerCert(authority);
633+
peerVerified = true;
634+
} catch (SSLPeerUnverifiedException | CertificateException e) {
635+
peerVerified = false;
636+
}
637+
authoritiesAllowedForPeer.put(authority, peerVerified);
638+
if (authoritiesAllowedForPeer.size() > MAX_AUTHORITIES_CACHE_SIZE) {
639+
authoritiesAllowedForPeer.remove(
640+
authoritiesAllowedForPeer.entrySet().iterator().next().getKey());
641+
}
642+
}
643+
return peerVerified;
644+
}
645+
646+
public void setSslEngine(SSLEngine sslEngine) {
647+
this.sslEngine = sslEngine;
648+
}
649+
618650
boolean canVerifyAuthorityOverride() {
619651
// sslEngine won't be set when creating ClientTlsHandlder from InternalProtocolNegotiators
620652
// for example.
621653
return sslEngine != null && x509ExtendedTrustManager != null;
622654
}
623655

624-
public void verifyAuthorityAllowedForPeerCert(String authority)
656+
private void verifyAuthorityAllowedForPeerCert(String authority)
625657
throws SSLPeerUnverifiedException, CertificateException {
626658
SSLEngine sslEngineWrapper = new SslEngineWrapper(sslEngine, authority);
627659
// The typecasting of Certificate to X509Certificate should work because this method will only
@@ -634,43 +666,10 @@ public void verifyAuthorityAllowedForPeerCert(String authority)
634666
x509ExtendedTrustManager.checkServerTrusted(x509PeerCertificates, "RSA", sslEngineWrapper);
635667
}
636668

637-
public void setSslEngine(SSLEngine sslEngine) {
638-
this.sslEngine = sslEngine;
639-
}
640-
641669
@VisibleForTesting
642670
boolean hasX509ExtendedTrustManager() {
643671
return x509ExtendedTrustManager != null;
644672
}
645-
646-
public boolean mayBeVerifyAuthority(@Nonnull String authority) {
647-
ClientTlsProtocolNegotiator clientTlsProtocolNegotiator =
648-
(ClientTlsProtocolNegotiator) negotiator;
649-
if (!clientTlsProtocolNegotiator.canVerifyAuthorityOverride()) {
650-
return new FailingClientStream(Status.INTERNAL.withDescription(
651-
"Can't allow authority override in rpc when X509ExtendedTrustManager is not available"),
652-
tracers);
653-
}
654-
boolean peerVerified;
655-
if (authoritiesAllowedForPeer.containsKey(callOptions.getAuthority())) {
656-
peerVerified = authoritiesAllowedForPeer.get(callOptions.getAuthority());
657-
} else {
658-
try {
659-
clientTlsProtocolNegotiator.verifyAuthorityAllowedForPeerCert(
660-
callOptions.getAuthority());
661-
peerVerified = true;
662-
} catch (SSLPeerUnverifiedException | CertificateException e) {
663-
peerVerified = false;
664-
logger.log(Level.FINE, "Peer hostname verification failed for authority '{}'.",
665-
callOptions.getAuthority());
666-
}
667-
authoritiesAllowedForPeer.put(callOptions.getAuthority(), peerVerified);
668-
}
669-
if (!peerVerified) {
670-
return new FailingClientStream(Status.INTERNAL.withDescription(
671-
"Peer hostname verification failed for authority"), tracers);
672-
}
673-
}
674673
}
675674

676675
static final class ClientTlsHandler extends ProtocolNegotiationHandler {

netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -891,8 +891,8 @@ Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.i
891891
InsightBuilder insightBuilder = new InsightBuilder();
892892
stream.appendTimeoutInsight(insightBuilder);
893893
assertThat(insightBuilder.toString()).contains(
894-
"Status{code=INTERNAL, description=Peer hostname verification failed for authority, "
895-
+ "cause=null}");
894+
"Status{code=INTERNAL, description=Peer hostname verification failed for authority"
895+
+ " 'foo.test.google.in'., cause=null}");
896896
}
897897

898898
@Test

netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626
import static org.junit.Assert.assertNull;
2727
import static org.junit.Assert.assertTrue;
2828
import static org.mockito.ArgumentMatchers.any;
29+
import static org.mockito.ArgumentMatchers.eq;
2930
import static org.mockito.Mockito.mock;
3031
import static org.mockito.Mockito.timeout;
3132
import static org.mockito.Mockito.times;
3233
import static org.mockito.Mockito.verify;
34+
import static org.mockito.Mockito.when;
3335

3436
import io.grpc.Attributes;
3537
import io.grpc.CallCredentials;
@@ -66,6 +68,7 @@
6668
import io.grpc.netty.ProtocolNegotiators.ClientTlsProtocolNegotiator;
6769
import io.grpc.netty.ProtocolNegotiators.HostPort;
6870
import io.grpc.netty.ProtocolNegotiators.ServerTlsHandler;
71+
import io.grpc.netty.ProtocolNegotiators.SslEngineWrapper;
6972
import io.grpc.netty.ProtocolNegotiators.WaitUntilActiveHandler;
7073
import io.grpc.testing.TlsTesting;
7174
import io.grpc.util.CertificateUtils;
@@ -118,6 +121,7 @@
118121
import java.security.KeyStore;
119122
import java.security.KeyStoreException;
120123
import java.security.NoSuchAlgorithmException;
124+
import java.security.cert.Certificate;
121125
import java.security.cert.CertificateException;
122126
import java.security.cert.X509Certificate;
123127
import java.util.ArrayDeque;
@@ -140,6 +144,7 @@
140144
import javax.net.ssl.SSLHandshakeException;
141145
import javax.net.ssl.SSLSession;
142146
import javax.net.ssl.TrustManagerFactory;
147+
import javax.net.ssl.X509ExtendedTrustManager;
143148
import org.junit.After;
144149
import org.junit.Before;
145150
import org.junit.BeforeClass;
@@ -1032,6 +1037,44 @@ private ClientTlsProtocolNegotiator getClientTlsProtocolNegotiator() throws SSLE
10321037
null, Optional.empty(), null);
10331038
}
10341039

1040+
@Test
1041+
public void allowedAuthoritiesForTransport_LruCache() throws SSLException, CertificateException {
1042+
X509ExtendedTrustManager mockX509ExtendedTrustManager = mock(X509ExtendedTrustManager.class);
1043+
ClientTlsProtocolNegotiator negotiator = new ClientTlsProtocolNegotiator(
1044+
GrpcSslContexts.forClient().trustManager(
1045+
TlsTesting.loadCert("ca.pem")).build(),
1046+
null, Optional.empty(), mockX509ExtendedTrustManager);
1047+
SSLEngine mockSslEngine = mock(SSLEngine.class);
1048+
negotiator.setSslEngine(mockSslEngine);
1049+
SSLSession mockSslSession = mock(SSLSession.class);
1050+
when(mockSslEngine.getSession()).thenReturn(mockSslSession);
1051+
when(mockSslSession.getPeerCertificates()).thenReturn(new Certificate[0]);
1052+
1053+
// Fill the cache
1054+
for (int i = 0; i < 100; i++) {
1055+
boolean unused = negotiator.mayBeVerifyAuthority("authority" + i);
1056+
}
1057+
// Should use value from the cache.
1058+
boolean unused = negotiator.mayBeVerifyAuthority("authority0");
1059+
// Should evict authority0.
1060+
unused = negotiator.mayBeVerifyAuthority("authority100");
1061+
// Should call TrustManager as the cached value has been evicted for this authority value.
1062+
unused = negotiator.mayBeVerifyAuthority("authority0");
1063+
1064+
ArgumentCaptor<SslEngineWrapper> sslEngineWrapperArgumentCaptor =
1065+
ArgumentCaptor.forClass(SslEngineWrapper.class);
1066+
verify(mockX509ExtendedTrustManager, times(102)).checkServerTrusted(any(), eq("RSA"),
1067+
sslEngineWrapperArgumentCaptor.capture());
1068+
List<SslEngineWrapper> sslEngineWrappersCaptured =
1069+
sslEngineWrapperArgumentCaptor.getAllValues();
1070+
assertThat(sslEngineWrappersCaptured).hasSize(102);
1071+
for (int i = 0; i < 100; i++) {
1072+
assertThat(sslEngineWrappersCaptured.get(i).getPeerHost()).isEqualTo("authority" + i);
1073+
}
1074+
assertThat(sslEngineWrappersCaptured.get(100).getPeerHost()).isEqualTo("authority100");
1075+
assertThat(sslEngineWrappersCaptured.get(101).getPeerHost()).isEqualTo("authority0");
1076+
}
1077+
10351078
@Test
10361079
public void engineLog() {
10371080
ChannelHandler handler = new ServerTlsHandler(grpcHandler, sslContext, null);

0 commit comments

Comments
 (0)