• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.netty;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.fail;
21 
22 import com.google.common.base.Throwables;
23 import com.google.common.util.concurrent.MoreExecutors;
24 import io.grpc.ManagedChannel;
25 import io.grpc.Server;
26 import io.grpc.ServerBuilder;
27 import io.grpc.Status;
28 import io.grpc.StatusRuntimeException;
29 import io.grpc.internal.testing.TestUtils;
30 import io.grpc.stub.StreamObserver;
31 import io.grpc.testing.protobuf.SimpleRequest;
32 import io.grpc.testing.protobuf.SimpleResponse;
33 import io.grpc.testing.protobuf.SimpleServiceGrpc;
34 import io.netty.handler.ssl.ClientAuth;
35 import io.netty.handler.ssl.OpenSsl;
36 import io.netty.handler.ssl.SslContext;
37 import io.netty.handler.ssl.SslContextBuilder;
38 import io.netty.handler.ssl.SslProvider;
39 import java.io.File;
40 import java.io.IOException;
41 import java.security.NoSuchAlgorithmException;
42 import java.security.Provider;
43 import java.security.Security;
44 import java.security.cert.X509Certificate;
45 import java.util.Arrays;
46 import java.util.concurrent.Executors;
47 import java.util.concurrent.ScheduledExecutorService;
48 import java.util.concurrent.TimeUnit;
49 import javax.net.ssl.SSLContext;
50 import org.junit.After;
51 import org.junit.Assume;
52 import org.junit.Before;
53 import org.junit.BeforeClass;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.junit.runners.Parameterized;
57 import org.junit.runners.Parameterized.Parameter;
58 import org.junit.runners.Parameterized.Parameters;
59 
60 
61 /**
62  * Integration tests for Netty's TLS support.
63  */
64 @RunWith(Parameterized.class)
65 public class TlsTest {
66 
67   public static enum TlsImpl {
68     TCNATIVE, JDK, CONSCRYPT;
69   }
70 
71   /**
72    * Iterable of various configurations to use for tests.
73    */
74   @Parameters(name = "{0}")
data()75   public static Iterable<Object[]> data() {
76     return Arrays.asList(new Object[][] {
77       {TlsImpl.TCNATIVE}, {TlsImpl.JDK}, {TlsImpl.CONSCRYPT},
78     });
79   }
80 
81   @Parameter(value = 0)
82   public TlsImpl tlsImpl;
83 
84   private ScheduledExecutorService executor;
85   private Server server;
86   private ManagedChannel channel;
87   private SslProvider sslProvider;
88   private Provider jdkProvider;
89   private SslContextBuilder clientContextBuilder;
90 
91   @BeforeClass
loadConscrypt()92   public static void loadConscrypt() {
93     TestUtils.installConscryptIfAvailable();
94   }
95 
96   @Before
setUp()97   public void setUp() throws NoSuchAlgorithmException {
98     executor = Executors.newSingleThreadScheduledExecutor();
99     switch (tlsImpl) {
100       case TCNATIVE:
101         Assume.assumeTrue(OpenSsl.isAvailable());
102         sslProvider = SslProvider.OPENSSL;
103         break;
104       case JDK:
105         Assume.assumeTrue(Arrays.asList(
106             SSLContext.getDefault().getSupportedSSLParameters().getCipherSuites())
107             .contains("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"));
108         sslProvider = SslProvider.JDK;
109         jdkProvider = Security.getProvider("SunJSSE");
110         Assume.assumeNotNull(jdkProvider);
111         try {
112           // Check for presence of an (ironic) class added in Java 9
113           Class.forName("java.lang.Runtime$Version");
114           // Java 9+
115         } catch (ClassNotFoundException ignored) {
116           // Before Java 9
117           try {
118             GrpcSslContexts.configure(SslContextBuilder.forClient(), jdkProvider);
119           } catch (IllegalArgumentException ex) {
120             Assume.assumeNoException("Not Java 9+ and Jetty ALPN does not seem available", ex);
121           }
122         }
123         break;
124       case CONSCRYPT:
125         sslProvider = SslProvider.JDK;
126         jdkProvider = Security.getProvider("Conscrypt");
127         Assume.assumeNotNull(jdkProvider);
128         break;
129       default:
130         throw new AssertionError();
131     }
132     clientContextBuilder = SslContextBuilder.forClient();
133     if (sslProvider == SslProvider.JDK) {
134       GrpcSslContexts.configure(clientContextBuilder, jdkProvider);
135     } else {
136       GrpcSslContexts.configure(clientContextBuilder, sslProvider);
137     }
138   }
139 
140   @After
tearDown()141   public void tearDown() {
142     if (server != null) {
143       server.shutdown();
144     }
145     if (channel != null) {
146       channel.shutdown();
147     }
148     MoreExecutors.shutdownAndAwaitTermination(executor, 5, TimeUnit.SECONDS);
149   }
150 
151 
152   /**
153    * Tests that a client and a server configured using GrpcSslContexts can successfully
154    * communicate with each other.
155    */
156   @Test
basicClientServerIntegrationTest()157   public void basicClientServerIntegrationTest() throws Exception {
158     // Create & start a server.
159     File serverCertFile = TestUtils.loadCert("server1.pem");
160     File serverPrivateKeyFile = TestUtils.loadCert("server1.key");
161     X509Certificate[] serverTrustedCaCerts = {
162       TestUtils.loadX509Cert("ca.pem")
163     };
164     server = serverBuilder(0, serverCertFile, serverPrivateKeyFile, serverTrustedCaCerts)
165         .addService(new SimpleServiceImpl())
166         .build()
167         .start();
168 
169     // Create a client.
170     File clientCertChainFile = TestUtils.loadCert("client.pem");
171     File clientPrivateKeyFile = TestUtils.loadCert("client.key");
172     X509Certificate[] clientTrustedCaCerts = {
173       TestUtils.loadX509Cert("ca.pem")
174     };
175     channel = clientChannel(server.getPort(), clientContextBuilder
176         .keyManager(clientCertChainFile, clientPrivateKeyFile)
177         .trustManager(clientTrustedCaCerts)
178         .build());
179     SimpleServiceGrpc.SimpleServiceBlockingStub client = SimpleServiceGrpc.newBlockingStub(channel);
180 
181     // Send an actual request, via the full GRPC & network stack, and check that a proper
182     // response comes back.
183     client.unaryRpc(SimpleRequest.getDefaultInstance());
184   }
185 
186   /**
187    * Tests that a server configured to require client authentication refuses to accept connections
188    * from a client that has an untrusted certificate.
189    */
190   @Test
serverRejectsUntrustedClientCert()191   public void serverRejectsUntrustedClientCert() throws Exception {
192     // Create & start a server. It requires client authentication and trusts only the test CA.
193     File serverCertFile = TestUtils.loadCert("server1.pem");
194     File serverPrivateKeyFile = TestUtils.loadCert("server1.key");
195     X509Certificate[] serverTrustedCaCerts = {
196       TestUtils.loadX509Cert("ca.pem")
197     };
198     server = serverBuilder(0, serverCertFile, serverPrivateKeyFile, serverTrustedCaCerts)
199         .addService(new SimpleServiceImpl())
200         .build()
201         .start();
202 
203     // Create a client. Its credentials come from a CA that the server does not trust. The client
204     // trusts both test CAs, so we can be sure that the handshake failure is due to the server
205     // rejecting the client's cert, not the client rejecting the server's cert.
206     File clientCertChainFile = TestUtils.loadCert("badclient.pem");
207     File clientPrivateKeyFile = TestUtils.loadCert("badclient.key");
208     X509Certificate[] clientTrustedCaCerts = {
209       TestUtils.loadX509Cert("ca.pem")
210     };
211     channel = clientChannel(server.getPort(), clientContextBuilder
212         .keyManager(clientCertChainFile, clientPrivateKeyFile)
213         .trustManager(clientTrustedCaCerts)
214         .build());
215     SimpleServiceGrpc.SimpleServiceBlockingStub client = SimpleServiceGrpc.newBlockingStub(channel);
216 
217     // Check that the TLS handshake fails.
218     try {
219       client.unaryRpc(SimpleRequest.getDefaultInstance());
220       fail("TLS handshake should have failed, but didn't; received RPC response");
221     } catch (StatusRuntimeException e) {
222       // GRPC reports this situation by throwing a StatusRuntimeException that wraps either a
223       // javax.net.ssl.SSLHandshakeException or a java.nio.channels.ClosedChannelException.
224       // Thus, reliably detecting the underlying cause is not feasible.
225       assertEquals(
226           Throwables.getStackTraceAsString(e),
227           Status.Code.UNAVAILABLE, e.getStatus().getCode());
228     }
229   }
230 
231 
232   /**
233    * Tests that a server configured to require client authentication actually does require client
234    * authentication.
235    */
236   @Test
noClientAuthFailure()237   public void noClientAuthFailure() throws Exception {
238     // Create & start a server.
239     File serverCertFile = TestUtils.loadCert("server1.pem");
240     File serverPrivateKeyFile = TestUtils.loadCert("server1.key");
241     X509Certificate[] serverTrustedCaCerts = {
242       TestUtils.loadX509Cert("ca.pem")
243     };
244     server = serverBuilder(0, serverCertFile, serverPrivateKeyFile, serverTrustedCaCerts)
245         .addService(new SimpleServiceImpl())
246         .build()
247         .start();
248 
249     // Create a client. It has no credentials.
250     X509Certificate[] clientTrustedCaCerts = {
251       TestUtils.loadX509Cert("ca.pem")
252     };
253     channel = clientChannel(server.getPort(), clientContextBuilder
254         .trustManager(clientTrustedCaCerts)
255         .build());
256     SimpleServiceGrpc.SimpleServiceBlockingStub client = SimpleServiceGrpc.newBlockingStub(channel);
257 
258     // Check that the TLS handshake fails.
259     try {
260       client.unaryRpc(SimpleRequest.getDefaultInstance());
261       fail("TLS handshake should have failed, but didn't; received RPC response");
262     } catch (StatusRuntimeException e) {
263       // GRPC reports this situation by throwing a StatusRuntimeException that wraps either a
264       // javax.net.ssl.SSLHandshakeException or a java.nio.channels.ClosedChannelException.
265       // Thus, reliably detecting the underlying cause is not feasible.
266       assertEquals(
267           Throwables.getStackTraceAsString(e),
268           Status.Code.UNAVAILABLE, e.getStatus().getCode());
269     }
270   }
271 
272 
273   /**
274    * Tests that a client configured using GrpcSslContexts refuses to talk to a server that has an
275    * an untrusted certificate.
276    */
277   @Test
clientRejectsUntrustedServerCert()278   public void clientRejectsUntrustedServerCert() throws Exception {
279     // Create & start a server.
280     File serverCertFile = TestUtils.loadCert("badserver.pem");
281     File serverPrivateKeyFile = TestUtils.loadCert("badserver.key");
282     X509Certificate[] serverTrustedCaCerts = {
283       TestUtils.loadX509Cert("ca.pem")
284     };
285     server = serverBuilder(0, serverCertFile, serverPrivateKeyFile, serverTrustedCaCerts)
286         .addService(new SimpleServiceImpl())
287         .build()
288         .start();
289 
290     // Create a client.
291     File clientCertChainFile = TestUtils.loadCert("client.pem");
292     File clientPrivateKeyFile = TestUtils.loadCert("client.key");
293     X509Certificate[] clientTrustedCaCerts = {
294       TestUtils.loadX509Cert("ca.pem")
295     };
296     channel = clientChannel(server.getPort(), clientContextBuilder
297         .keyManager(clientCertChainFile, clientPrivateKeyFile)
298         .trustManager(clientTrustedCaCerts)
299         .build());
300     SimpleServiceGrpc.SimpleServiceBlockingStub client = SimpleServiceGrpc.newBlockingStub(channel);
301 
302     // Check that the TLS handshake fails.
303     try {
304       client.unaryRpc(SimpleRequest.getDefaultInstance());
305       fail("TLS handshake should have failed, but didn't; received RPC response");
306     } catch (StatusRuntimeException e) {
307       // GRPC reports this situation by throwing a StatusRuntimeException that wraps either a
308       // javax.net.ssl.SSLHandshakeException or a java.nio.channels.ClosedChannelException.
309       // Thus, reliably detecting the underlying cause is not feasible.
310       // TODO(carl-mastrangelo): eventually replace this with a hamcrest matcher.
311       assertEquals(
312           Throwables.getStackTraceAsString(e),
313           Status.Code.UNAVAILABLE, e.getStatus().getCode());
314     }
315   }
316 
317 
serverBuilder(int port, File serverCertChainFile, File serverPrivateKeyFile, X509Certificate[] serverTrustedCaCerts)318   private ServerBuilder<?> serverBuilder(int port, File serverCertChainFile,
319       File serverPrivateKeyFile, X509Certificate[] serverTrustedCaCerts) throws IOException {
320     SslContextBuilder sslContextBuilder
321         = SslContextBuilder.forServer(serverCertChainFile, serverPrivateKeyFile);
322     if (sslProvider == SslProvider.JDK) {
323       GrpcSslContexts.configure(sslContextBuilder, jdkProvider);
324     } else {
325       GrpcSslContexts.configure(sslContextBuilder, sslProvider);
326     }
327     sslContextBuilder.trustManager(serverTrustedCaCerts)
328         .clientAuth(ClientAuth.REQUIRE);
329 
330     return NettyServerBuilder.forPort(port)
331         .sslContext(sslContextBuilder.build());
332   }
333 
334 
clientChannel(int port, SslContext sslContext)335   private static ManagedChannel clientChannel(int port, SslContext sslContext) throws IOException {
336     return NettyChannelBuilder.forAddress("localhost", port)
337         .overrideAuthority(TestUtils.TEST_SERVER_HOST)
338         .negotiationType(NegotiationType.TLS)
339         .sslContext(sslContext)
340         .build();
341   }
342 
343   private static class SimpleServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase {
344     @Override
unaryRpc(SimpleRequest req, StreamObserver<SimpleResponse> respOb)345     public void unaryRpc(SimpleRequest req, StreamObserver<SimpleResponse> respOb) {
346       respOb.onNext(SimpleResponse.getDefaultInstance());
347       respOb.onCompleted();
348     }
349   }
350 }
351