From 46118bb195ce7e0bf865a9bf8ab01b49d3d49dcb Mon Sep 17 00:00:00 2001 From: ejona Date: Thu, 23 Oct 2014 12:05:19 -0700 Subject: [PATCH] Add server support for TLS. Note that we don't yet have plumbing to use a particular certificate for tests, so it isn't integration-test worthy yet. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=78369023 --- .../net/stubby/AbstractServiceBuilder.java | 2 +- .../newtransport/netty/Http2Negotiator.java | 62 ++++++++++++++----- .../newtransport/netty/NettyServer.java | 13 ++-- .../netty/NettyServerBuilder.java | 13 +++- .../netty/NettyServerTransport.java | 11 +++- 5 files changed, 77 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/com/google/net/stubby/AbstractServiceBuilder.java b/core/src/main/java/com/google/net/stubby/AbstractServiceBuilder.java index 3b7616a594..42074d3c9a 100644 --- a/core/src/main/java/com/google/net/stubby/AbstractServiceBuilder.java +++ b/core/src/main/java/com/google/net/stubby/AbstractServiceBuilder.java @@ -44,7 +44,7 @@ abstract class AbstractServiceBuilderThe returned service has not been started at this point. You will need to start it by * yourself or use {@link #buildAndStart()}. */ - private ProductT build() { + public ProductT build() { final ExecutorService executor = (userExecutor == null) ? Executors.newCachedThreadPool() : userExecutor; ProductT service = buildImpl(executor); diff --git a/core/src/main/java/com/google/net/stubby/newtransport/netty/Http2Negotiator.java b/core/src/main/java/com/google/net/stubby/newtransport/netty/Http2Negotiator.java index 16adae6739..4ffa7448f0 100644 --- a/core/src/main/java/com/google/net/stubby/newtransport/netty/Http2Negotiator.java +++ b/core/src/main/java/com/google/net/stubby/newtransport/netty/Http2Negotiator.java @@ -64,6 +64,17 @@ public class Http2Negotiator { ListenableFuture completeFuture(); } + /** + * Create a TLS handler for HTTP/2 capable of using ALPN/NPN. + */ + public static ChannelHandler serverTls(SSLEngine sslEngine) { + Preconditions.checkNotNull(sslEngine, "sslEngine"); + if (!installJettyTLSProtocolSelection(sslEngine, SettableFuture.create(), true)) { + throw new IllegalStateException("NPN/ALPN extensions not installed"); + } + return new SslHandler(sslEngine, false); + } + /** * Creates an TLS negotiation for HTTP/2 using ALPN/NPN. */ @@ -72,7 +83,7 @@ public class Http2Negotiator { Preconditions.checkNotNull(sslEngine, "sslEngine"); final SettableFuture completeFuture = SettableFuture.create(); - if (!installJettyTLSProtocolSelection(sslEngine, completeFuture)) { + if (!installJettyTLSProtocolSelection(sslEngine, completeFuture, false)) { throw new IllegalStateException("NPN/ALPN extensions not installed"); } final ChannelInitializer initializer = new ChannelInitializer() { @@ -236,7 +247,7 @@ public class Http2Negotiator { * @return true if NPN/ALPN support is available. */ private static boolean installJettyTLSProtocolSelection(final SSLEngine engine, - final SettableFuture protocolNegotiated) { + final SettableFuture protocolNegotiated, boolean server) { for (String protocolNegoClassName : JETTY_TLS_NEGOTIATION_IMPL) { try { Class negoClass; @@ -249,38 +260,53 @@ public class Http2Negotiator { } Class providerClass = Class.forName(protocolNegoClassName + "$Provider"); Class clientProviderClass = Class.forName(protocolNegoClassName + "$ClientProvider"); + Class serverProviderClass = Class.forName(protocolNegoClassName + "$ServerProvider"); Method putMethod = negoClass.getMethod("put", SSLEngine.class, providerClass); final Method removeMethod = negoClass.getMethod("remove", SSLEngine.class); putMethod.invoke(null, engine, Proxy.newProxyInstance( - Http2Negotiator.class.getClassLoader(), new Class[] {clientProviderClass}, + Http2Negotiator.class.getClassLoader(), + new Class[] {server ? serverProviderClass : clientProviderClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if ("supports".equals(methodName)) { - // both + // NPN client return true; } if ("unsupported".equals(methodName)) { - // both + // all removeMethod.invoke(null, engine); - protocolNegotiated.setException(new IllegalStateException( - "ALPN/NPN protocol " + HTTP_VERSION_NAME + " not supported by server")); + protocolNegotiated.setException(new RuntimeException( + "ALPN/NPN protocol " + HTTP_VERSION_NAME + " not supported by endpoint")); return null; } if ("protocols".equals(methodName)) { - // ALPN only + // ALPN client, NPN server return ImmutableList.of(HTTP_VERSION_NAME); } - if ("selected".equals(methodName)) { - // ALPN only - // Only 'supports' one protocol so we know what was selected. + if ("selected".equals(methodName) || "protocolSelected".equals(methodName)) { + // ALPN client, NPN server removeMethod.invoke(null, engine); + String protocol = (String) args[0]; + if (!HTTP_VERSION_NAME.equals(protocol)) { + RuntimeException e = new RuntimeException( + "Unsupported protocol selected via ALPN/NPN: " + protocol); + protocolNegotiated.setException(e); + if ("selected".equals(methodName)) { + // ALPN client + // Throwing exception causes TLS alert. + throw e; + } else { + return null; + } + } protocolNegotiated.set(null); return null; } - if ("selectProtocol".equals(methodName)) { - // NPN only + if ("select".equals(methodName) || "selectProtocol".equals(methodName)) { + // ALPN server, NPN client + removeMethod.invoke(null, engine); @SuppressWarnings("unchecked") List names = (List) args[0]; for (String name : names) { @@ -289,9 +315,13 @@ public class Http2Negotiator { return name; } } - protocolNegotiated.setException( - new IllegalStateException("Protocol not available via ALPN/NPN: " + names)); - removeMethod.invoke(null, engine); + RuntimeException e = + new RuntimeException("Protocol not available via ALPN/NPN: " + names); + protocolNegotiated.setException(e); + if ("select".equals(methodName)) { + // ALPN server + throw e; // Throwing exception causes TLS alert. + } return null; } throw new IllegalStateException("Unknown method " + methodName); diff --git a/core/src/main/java/com/google/net/stubby/newtransport/netty/NettyServer.java b/core/src/main/java/com/google/net/stubby/newtransport/netty/NettyServer.java index e50ad56254..8b6a62cd20 100644 --- a/core/src/main/java/com/google/net/stubby/newtransport/netty/NettyServer.java +++ b/core/src/main/java/com/google/net/stubby/newtransport/netty/NettyServer.java @@ -13,9 +13,11 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.ssl.SslContext; + +import javax.annotation.Nullable; /** * Implementation of the {@link com.google.common.util.concurrent.Service} interface for a @@ -28,12 +30,13 @@ public class NettyServer extends AbstractService { private final EventLoopGroup workerGroup; private Channel channel; - public NettyServer(ServerListener serverListener, int port) { - this(serverListener, port, new NioEventLoopGroup(), new NioEventLoopGroup()); + public NettyServer(ServerListener serverListener, int port, EventLoopGroup bossGroup, + EventLoopGroup workerGroup) { + this(serverListener, port, bossGroup, workerGroup, null); } public NettyServer(final ServerListener serverListener, int port, EventLoopGroup bossGroup, - EventLoopGroup workerGroup) { + EventLoopGroup workerGroup, @Nullable final SslContext sslContext) { Preconditions.checkNotNull(bossGroup, "bossGroup"); Preconditions.checkNotNull(workerGroup, "workerGroup"); Preconditions.checkArgument(port >= 0, "port must be positive"); @@ -41,7 +44,7 @@ public class NettyServer extends AbstractService { this.channelInitializer = new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { - NettyServerTransport transport = new NettyServerTransport(ch, serverListener); + NettyServerTransport transport = new NettyServerTransport(ch, serverListener, sslContext); transport.startAsync(); // TODO(user): Should we wait for transport shutdown before shutting down server? } diff --git a/core/src/main/java/com/google/net/stubby/newtransport/netty/NettyServerBuilder.java b/core/src/main/java/com/google/net/stubby/newtransport/netty/NettyServerBuilder.java index 7a5bdabc43..e13e4a644e 100644 --- a/core/src/main/java/com/google/net/stubby/newtransport/netty/NettyServerBuilder.java +++ b/core/src/main/java/com/google/net/stubby/newtransport/netty/NettyServerBuilder.java @@ -8,6 +8,7 @@ import com.google.net.stubby.newtransport.ServerListener; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.handler.ssl.SslContext; /** * The convenient builder for a netty-based GRPC server. @@ -18,6 +19,7 @@ public final class NettyServerBuilder extends AbstractServerBuilder