|
59 | 59 | import io.grpc.internal.ClientStream; |
60 | 60 | import io.grpc.internal.ClientStreamListener; |
61 | 61 | import io.grpc.internal.ClientTransport; |
| 62 | +import io.grpc.internal.FailingClientStream; |
62 | 63 | import io.grpc.internal.FakeClock; |
63 | 64 | import io.grpc.internal.FixedObjectPool; |
64 | 65 | import io.grpc.internal.GrpcUtil; |
| 66 | +import io.grpc.internal.InsightBuilder; |
65 | 67 | import io.grpc.internal.ManagedClientTransport; |
66 | 68 | import io.grpc.internal.ServerListener; |
67 | 69 | import io.grpc.internal.ServerStream; |
|
73 | 75 | import io.grpc.netty.NettyChannelBuilder.LocalSocketPicker; |
74 | 76 | import io.grpc.netty.NettyTestUtil.TrackingObjectPoolForTest; |
75 | 77 | import io.grpc.testing.TlsTesting; |
| 78 | +import io.grpc.util.CertificateUtils; |
76 | 79 | import io.netty.buffer.ByteBuf; |
77 | 80 | import io.netty.channel.Channel; |
78 | 81 | import io.netty.channel.ChannelConfig; |
|
100 | 103 | import java.net.InetSocketAddress; |
101 | 104 | import java.net.SocketAddress; |
102 | 105 | import java.nio.charset.StandardCharsets; |
| 106 | +import java.security.GeneralSecurityException; |
| 107 | +import java.security.KeyStore; |
| 108 | +import java.security.cert.CertificateException; |
| 109 | +import java.security.cert.X509Certificate; |
103 | 110 | import java.util.ArrayList; |
| 111 | +import java.util.Arrays; |
104 | 112 | import java.util.Collections; |
105 | 113 | import java.util.HashMap; |
106 | 114 | import java.util.List; |
|
114 | 122 | import javax.annotation.Nullable; |
115 | 123 | import javax.net.ssl.SSLException; |
116 | 124 | import javax.net.ssl.SSLHandshakeException; |
| 125 | +import javax.net.ssl.TrustManager; |
| 126 | +import javax.net.ssl.TrustManagerFactory; |
| 127 | +import javax.net.ssl.X509ExtendedTrustManager; |
| 128 | +import javax.net.ssl.X509TrustManager; |
| 129 | +import javax.security.auth.x500.X500Principal; |
117 | 130 | import org.junit.After; |
118 | 131 | import org.junit.Before; |
119 | 132 | import org.junit.Rule; |
@@ -199,7 +212,7 @@ public void addDefaultUserAgent() throws Exception { |
199 | 212 | } |
200 | 213 |
|
201 | 214 | @Test |
202 | | - public void setSoLingerChannelOption() throws IOException { |
| 215 | + public void setSoLingerChannelOption() throws IOException, GeneralSecurityException { |
203 | 216 | startServer(); |
204 | 217 | Map<ChannelOption<?>, Object> channelOptions = new HashMap<>(); |
205 | 218 | // set SO_LINGER option |
@@ -817,17 +830,86 @@ public void tlsNegotiationServerExecutorShouldSucceed() throws Exception { |
817 | 830 | assertEquals(false, serverExecutorPool.isInUse()); |
818 | 831 | } |
819 | 832 |
|
| 833 | + @Test |
| 834 | + public void authorityOverrideInCallOptions_doesntMatchServerPeerHost_newStreamCreationFails() |
| 835 | + throws IOException, InterruptedException, GeneralSecurityException { |
| 836 | + startServer(); |
| 837 | + NettyClientTransport transport = newTransport(newNegotiator()); |
| 838 | + FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); |
| 839 | + callMeMaybe(transport.start(fakeClientTransportListener)); |
| 840 | + synchronized (fakeClientTransportListener) { |
| 841 | + fakeClientTransportListener.wait(10000); |
| 842 | + } |
| 843 | + assertThat(fakeClientTransportListener.isConnected).isTrue(); |
| 844 | + |
| 845 | + ClientStream stream = transport.newStream( |
| 846 | + Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), |
| 847 | + new ClientStreamTracer[]{new ClientStreamTracer() { |
| 848 | + }}); |
| 849 | + |
| 850 | + assertThat(stream).isInstanceOf(FailingClientStream.class); |
| 851 | + InsightBuilder insightBuilder = new InsightBuilder(); |
| 852 | + stream.appendTimeoutInsight(insightBuilder); |
| 853 | + assertThat(insightBuilder.toString()).contains( |
| 854 | + "Status{code=INTERNAL, description=Peer hostname verification failed for authority, " |
| 855 | + + "cause=null}"); |
| 856 | + } |
| 857 | + |
| 858 | + @Test |
| 859 | + public void authorityOverrideInCallOptions_matchesServerPeerHost_newStreamCreationSucceeds() |
| 860 | + throws IOException, InterruptedException, GeneralSecurityException { |
| 861 | + startServer(); |
| 862 | + NettyClientTransport transport = newTransport(newNegotiator()); |
| 863 | + FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); |
| 864 | + callMeMaybe(transport.start(fakeClientTransportListener)); |
| 865 | + synchronized (fakeClientTransportListener) { |
| 866 | + fakeClientTransportListener.wait(10000); |
| 867 | + } |
| 868 | + assertThat(fakeClientTransportListener.isConnected).isTrue(); |
| 869 | + |
| 870 | + ClientStream stream = transport.newStream( |
| 871 | + Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("zoo.test.google.fr"), |
| 872 | + new ClientStreamTracer[]{new ClientStreamTracer() { |
| 873 | + }}); |
| 874 | + |
| 875 | + assertThat(stream).isNotInstanceOf(FailingClientStream.class); |
| 876 | + } |
| 877 | + |
820 | 878 | private Throwable getRootCause(Throwable t) { |
821 | 879 | if (t.getCause() == null) { |
822 | 880 | return t; |
823 | 881 | } |
824 | 882 | return getRootCause(t.getCause()); |
825 | 883 | } |
826 | 884 |
|
827 | | - private ProtocolNegotiator newNegotiator() throws IOException { |
| 885 | + private ProtocolNegotiator newNegotiator() throws IOException, GeneralSecurityException { |
828 | 886 | InputStream caCert = TlsTesting.loadCert("ca.pem"); |
829 | 887 | SslContext clientContext = GrpcSslContexts.forClient().trustManager(caCert).build(); |
830 | | - return ProtocolNegotiators.tls(clientContext, null); |
| 888 | + return ProtocolNegotiators.tls(clientContext, |
| 889 | + (X509ExtendedTrustManager) getX509ExtendedTrustManager( |
| 890 | + TlsTesting.loadCert("ca.pem")).get()); |
| 891 | + } |
| 892 | + |
| 893 | + private static Optional<TrustManager> getX509ExtendedTrustManager(InputStream rootCerts) |
| 894 | + throws GeneralSecurityException { |
| 895 | + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); |
| 896 | + try { |
| 897 | + ks.load(null, null); |
| 898 | + } catch (IOException ex) { |
| 899 | + // Shouldn't really happen, as we're not loading any data. |
| 900 | + throw new GeneralSecurityException(ex); |
| 901 | + } |
| 902 | + X509Certificate[] certs = CertificateUtils.getX509Certificates(rootCerts); |
| 903 | + for (X509Certificate cert : certs) { |
| 904 | + X500Principal principal = cert.getSubjectX500Principal(); |
| 905 | + ks.setCertificateEntry(principal.getName("RFC2253"), cert); |
| 906 | + } |
| 907 | + |
| 908 | + TrustManagerFactory trustManagerFactory = |
| 909 | + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); |
| 910 | + trustManagerFactory.init(ks); |
| 911 | + return Arrays.stream(trustManagerFactory.getTrustManagers()) |
| 912 | + .filter(trustManager -> trustManager instanceof X509ExtendedTrustManager).findFirst(); |
831 | 913 | } |
832 | 914 |
|
833 | 915 | private NettyClientTransport newTransport(ProtocolNegotiator negotiator) { |
@@ -951,7 +1033,7 @@ private static class Rpc { |
951 | 1033 |
|
952 | 1034 | Rpc(NettyClientTransport transport, Metadata headers) { |
953 | 1035 | stream = transport.newStream( |
954 | | - METHOD, headers, CallOptions.DEFAULT.withAuthority("wrong-authority"), |
| 1036 | + METHOD, headers, CallOptions.DEFAULT, |
955 | 1037 | new ClientStreamTracer[]{ new ClientStreamTracer() {} }); |
956 | 1038 | stream.start(listener); |
957 | 1039 | stream.request(1); |
@@ -1144,4 +1226,51 @@ public void log(ChannelLogLevel level, String message) {} |
1144 | 1226 | @Override |
1145 | 1227 | public void log(ChannelLogLevel level, String messageFormat, Object... args) {} |
1146 | 1228 | } |
| 1229 | + |
| 1230 | + static class FakeTrustManager implements X509TrustManager { |
| 1231 | + |
| 1232 | + @Override |
| 1233 | + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) |
| 1234 | + throws CertificateException { |
| 1235 | + |
| 1236 | + } |
| 1237 | + |
| 1238 | + @Override |
| 1239 | + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) |
| 1240 | + throws CertificateException { |
| 1241 | + |
| 1242 | + } |
| 1243 | + |
| 1244 | + @Override |
| 1245 | + public X509Certificate[] getAcceptedIssuers() { |
| 1246 | + return new X509Certificate[0]; |
| 1247 | + } |
| 1248 | + } |
| 1249 | + |
| 1250 | + static class FakeClientTransportListener implements ManagedClientTransport.Listener { |
| 1251 | + private boolean isConnected = false; |
| 1252 | + |
| 1253 | + @Override |
| 1254 | + public void transportShutdown(Status s) { |
| 1255 | + |
| 1256 | + } |
| 1257 | + |
| 1258 | + @Override |
| 1259 | + public void transportTerminated() { |
| 1260 | + |
| 1261 | + } |
| 1262 | + |
| 1263 | + @Override |
| 1264 | + public void transportReady() { |
| 1265 | + isConnected = true; |
| 1266 | + synchronized (this) { |
| 1267 | + notify(); |
| 1268 | + } |
| 1269 | + } |
| 1270 | + |
| 1271 | + @Override |
| 1272 | + public void transportInUse(boolean inUse) { |
| 1273 | + |
| 1274 | + } |
| 1275 | + } |
1147 | 1276 | } |
0 commit comments