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