View Javadoc
1   /*
2    * Copyright 2017 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. 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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.ssl;
17  
18  import static io.netty.handler.ssl.SslUtils.toSSLHandshakeException;
19  import static io.netty.util.internal.ObjectUtil.checkNotNull;
20  import static java.lang.Math.min;
21  
22  import io.netty.buffer.ByteBuf;
23  import io.netty.buffer.ByteBufAllocator;
24  import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelectionListener;
25  import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelector;
26  import java.nio.ByteBuffer;
27  import java.util.Collections;
28  import java.util.LinkedHashSet;
29  import java.util.List;
30  import javax.net.ssl.SSLEngine;
31  import javax.net.ssl.SSLEngineResult;
32  import javax.net.ssl.SSLException;
33  
34  import io.netty.util.internal.SystemPropertyUtil;
35  import org.conscrypt.AllocatedBuffer;
36  import org.conscrypt.BufferAllocator;
37  import org.conscrypt.Conscrypt;
38  import org.conscrypt.HandshakeListener;
39  
40  /**
41   * A {@link JdkSslEngine} that uses the Conscrypt provider or SSL with ALPN.
42   */
43  abstract class ConscryptAlpnSslEngine extends JdkSslEngine {
44      private static final boolean USE_BUFFER_ALLOCATOR = SystemPropertyUtil.getBoolean(
45              "io.netty.handler.ssl.conscrypt.useBufferAllocator", true);
46  
47      static ConscryptAlpnSslEngine newClientEngine(SSLEngine engine, ByteBufAllocator alloc,
48              JdkApplicationProtocolNegotiator applicationNegotiator) {
49          return new ClientEngine(engine, alloc, applicationNegotiator);
50      }
51  
52      static ConscryptAlpnSslEngine newServerEngine(SSLEngine engine, ByteBufAllocator alloc,
53              JdkApplicationProtocolNegotiator applicationNegotiator) {
54          return new ServerEngine(engine, alloc, applicationNegotiator);
55      }
56  
57      private ConscryptAlpnSslEngine(SSLEngine engine, ByteBufAllocator alloc, List<String> protocols) {
58          super(engine);
59  
60          // Configure the Conscrypt engine to use Netty's buffer allocator. This is a trade-off of memory vs
61          // performance.
62          //
63          // If no allocator is provided, the engine will internally allocate a direct buffer of max packet size in
64          // order to optimize JNI calls (this happens the first time it is provided a non-direct buffer from the
65          // application).
66          //
67          // Alternatively, if an allocator is provided, no internal buffer will be created and direct buffers will be
68          // retrieved from the allocator on-demand.
69          if (USE_BUFFER_ALLOCATOR) {
70              Conscrypt.setBufferAllocator(engine, new BufferAllocatorAdapter(alloc));
71          }
72  
73          // Set the list of supported ALPN protocols on the engine.
74          Conscrypt.setApplicationProtocols(engine, protocols.toArray(new String[0]));
75      }
76  
77      /**
78       * Calculates the maximum size of the encrypted output buffer required to wrap the given plaintext bytes. Assumes
79       * as a worst case that there is one TLS record per buffer.
80       *
81       * @param plaintextBytes the number of plaintext bytes to be wrapped.
82       * @param numBuffers the number of buffers that the plaintext bytes are spread across.
83       * @return the maximum size of the encrypted output buffer required for the wrap operation.
84       */
85      final int calculateOutNetBufSize(int plaintextBytes, int numBuffers) {
86          // Assuming a max of one frame per component in a composite buffer.
87          long maxOverhead = (long) Conscrypt.maxSealOverhead(getWrappedEngine()) * numBuffers;
88          // TODO(nmittler): update this to use MAX_ENCRYPTED_PACKET_LENGTH instead of Integer.MAX_VALUE
89          return (int) min(Integer.MAX_VALUE, plaintextBytes + maxOverhead);
90      }
91  
92      final SSLEngineResult unwrap(ByteBuffer[] srcs, ByteBuffer[] dests) throws SSLException {
93          return Conscrypt.unwrap(getWrappedEngine(), srcs, dests);
94      }
95  
96      private static final class ClientEngine extends ConscryptAlpnSslEngine {
97          private final ProtocolSelectionListener protocolListener;
98  
99          ClientEngine(SSLEngine engine, ByteBufAllocator alloc,
100                 JdkApplicationProtocolNegotiator applicationNegotiator) {
101             super(engine, alloc, applicationNegotiator.protocols());
102             // Register for completion of the handshake.
103             Conscrypt.setHandshakeListener(engine, new HandshakeListener() {
104                 @Override
105                 public void onHandshakeFinished() throws SSLException {
106                     selectProtocol();
107                 }
108             });
109 
110             protocolListener = checkNotNull(applicationNegotiator
111                             .protocolListenerFactory().newListener(this, applicationNegotiator.protocols()),
112                     "protocolListener");
113         }
114 
115         private void selectProtocol() throws SSLException {
116             String protocol = Conscrypt.getApplicationProtocol(getWrappedEngine());
117             try {
118                 protocolListener.selected(protocol);
119             } catch (Throwable e) {
120                 throw toSSLHandshakeException(e);
121             }
122         }
123     }
124 
125     private static final class ServerEngine extends ConscryptAlpnSslEngine {
126         private final ProtocolSelector protocolSelector;
127 
128         ServerEngine(SSLEngine engine, ByteBufAllocator alloc,
129                      JdkApplicationProtocolNegotiator applicationNegotiator) {
130             super(engine, alloc, applicationNegotiator.protocols());
131 
132             // Register for completion of the handshake.
133             Conscrypt.setHandshakeListener(engine, new HandshakeListener() {
134                 @Override
135                 public void onHandshakeFinished() throws SSLException {
136                     selectProtocol();
137                 }
138             });
139 
140             protocolSelector = checkNotNull(applicationNegotiator.protocolSelectorFactory()
141                             .newSelector(this,
142                                     new LinkedHashSet<String>(applicationNegotiator.protocols())),
143                     "protocolSelector");
144         }
145 
146         private void selectProtocol() throws SSLException {
147             try {
148                 String protocol = Conscrypt.getApplicationProtocol(getWrappedEngine());
149                 protocolSelector.select(protocol != null ? Collections.singletonList(protocol)
150                         : Collections.<String>emptyList());
151             } catch (Throwable e) {
152                 throw toSSLHandshakeException(e);
153             }
154         }
155     }
156 
157     private static final class BufferAllocatorAdapter extends BufferAllocator {
158         private final ByteBufAllocator alloc;
159 
160         BufferAllocatorAdapter(ByteBufAllocator alloc) {
161             this.alloc = alloc;
162         }
163 
164         @Override
165         public AllocatedBuffer allocateDirectBuffer(int capacity) {
166             return new BufferAdapter(alloc.directBuffer(capacity));
167         }
168     }
169 
170     private static final class BufferAdapter extends AllocatedBuffer {
171         private final ByteBuf nettyBuffer;
172         private final ByteBuffer buffer;
173 
174         BufferAdapter(ByteBuf nettyBuffer) {
175             this.nettyBuffer = nettyBuffer;
176             buffer = nettyBuffer.nioBuffer(0, nettyBuffer.capacity());
177         }
178 
179         @Override
180         public ByteBuffer nioBuffer() {
181             return buffer;
182         }
183 
184         @Override
185         public AllocatedBuffer retain() {
186             nettyBuffer.retain();
187             return this;
188         }
189 
190         @Override
191         public AllocatedBuffer release() {
192             nettyBuffer.release();
193             return this;
194         }
195     }
196 }