View Javadoc
1   /*
2    * Copyright 2014 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.netty.internal.tcnative.SSL;
19  import io.netty.internal.tcnative.SSLContext;
20  import io.netty.internal.tcnative.SessionTicketKey;
21  import io.netty5.util.internal.ObjectUtil;
22  
23  import javax.net.ssl.SSLSession;
24  import javax.net.ssl.SSLSessionContext;
25  import java.util.Arrays;
26  import java.util.Enumeration;
27  import java.util.Iterator;
28  import java.util.concurrent.locks.Lock;
29  
30  import static java.util.Objects.requireNonNull;
31  
32  /**
33   * OpenSSL specific {@link SSLSessionContext} implementation.
34   */
35  public abstract class OpenSslSessionContext implements SSLSessionContext {
36  
37      private final OpenSslSessionStats stats;
38  
39      // The OpenSslKeyMaterialProvider is not really used by the OpenSslSessionContext but only be stored here
40      // to make it easier to destroy it later because the ReferenceCountedOpenSslContext will hold a reference
41      // to OpenSslSessionContext.
42      private final OpenSslKeyMaterialProvider provider;
43  
44      final ReferenceCountedOpenSslContext context;
45  
46      private final OpenSslSessionCache sessionCache;
47      private final long mask;
48  
49      // IMPORTANT: We take the OpenSslContext and not just the long (which points the native instance) to prevent
50      //            the GC to collect OpenSslContext as this would also free the pointer and so could result in a
51      //            segfault when the user calls any of the methods here that try to pass the pointer down to the native
52      //            level.
53      OpenSslSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider, long mask,
54                            OpenSslSessionCache cache) {
55          this.context = context;
56          this.provider = provider;
57          this.mask = mask;
58          stats = new OpenSslSessionStats(context);
59          sessionCache = cache;
60          SSLContext.setSSLSessionCache(context.ctx, cache);
61      }
62  
63      final boolean useKeyManager() {
64          return provider != null;
65      }
66  
67      @Override
68      public void setSessionCacheSize(int size) {
69          ObjectUtil.checkPositiveOrZero(size, "size");
70          sessionCache.setSessionCacheSize(size);
71      }
72  
73      @Override
74      public int getSessionCacheSize() {
75          return sessionCache.getSessionCacheSize();
76      }
77  
78      @Override
79      public void setSessionTimeout(int seconds) {
80          ObjectUtil.checkPositiveOrZero(seconds, "seconds");
81  
82          Lock writerLock = context.ctxLock.writeLock();
83          writerLock.lock();
84          try {
85              SSLContext.setSessionCacheTimeout(context.ctx, seconds);
86              sessionCache.setSessionTimeout(seconds);
87          } finally {
88              writerLock.unlock();
89          }
90      }
91  
92      @Override
93      public int getSessionTimeout() {
94          return sessionCache.getSessionTimeout();
95      }
96  
97      @Override
98      public SSLSession getSession(byte[] bytes) {
99          return sessionCache.getSession(new OpenSslSessionId(bytes));
100     }
101 
102     @Override
103     public Enumeration<byte[]> getIds() {
104         return new Enumeration<>() {
105             private final Iterator<OpenSslSessionId> ids = sessionCache.getIds().iterator();
106 
107             @Override
108             public boolean hasMoreElements() {
109                 return ids.hasNext();
110             }
111 
112             @Override
113             public byte[] nextElement() {
114                 return ids.next().cloneBytes();
115             }
116         };
117     }
118 
119     /**
120      * Sets the SSL session ticket keys of this context.
121      * @deprecated use {@link #setTicketKeys(OpenSslSessionTicketKey...)}.
122      */
123     @Deprecated
124     public void setTicketKeys(byte[] keys) {
125         if (keys.length % SessionTicketKey.TICKET_KEY_SIZE != 0) {
126             throw new IllegalArgumentException("keys.length % " + SessionTicketKey.TICKET_KEY_SIZE  + " != 0");
127         }
128         SessionTicketKey[] tickets = new SessionTicketKey[keys.length / SessionTicketKey.TICKET_KEY_SIZE];
129         for (int i = 0, a = 0; i < tickets.length; i++) {
130             byte[] name = Arrays.copyOfRange(keys, a, SessionTicketKey.NAME_SIZE);
131             a += SessionTicketKey.NAME_SIZE;
132             byte[] hmacKey = Arrays.copyOfRange(keys, a, SessionTicketKey.HMAC_KEY_SIZE);
133             i += SessionTicketKey.HMAC_KEY_SIZE;
134             byte[] aesKey = Arrays.copyOfRange(keys, a, SessionTicketKey.AES_KEY_SIZE);
135             a += SessionTicketKey.AES_KEY_SIZE;
136             tickets[i] = new SessionTicketKey(name, hmacKey, aesKey);
137         }
138         Lock writerLock = context.ctxLock.writeLock();
139         writerLock.lock();
140         try {
141             SSLContext.clearOptions(context.ctx, SSL.SSL_OP_NO_TICKET);
142             SSLContext.setSessionTicketKeys(context.ctx, tickets);
143         } finally {
144             writerLock.unlock();
145         }
146     }
147 
148     /**
149      * Sets the SSL session ticket keys of this context. Depending on the underlying native library you may omit the
150      * argument or pass an empty array and so let the native library handle the key generation and rotating for you.
151      * If this is supported by the underlying native library should be checked in this case. For example
152      * <a href="https://boringssl.googlesource.com/boringssl/">BoringSSL</a> is known to support this.
153      */
154     public void setTicketKeys(OpenSslSessionTicketKey... keys) {
155         requireNonNull(keys, "keys");
156         SessionTicketKey[] ticketKeys = new SessionTicketKey[keys.length];
157         for (int i = 0; i < ticketKeys.length; i++) {
158             ticketKeys[i] = keys[i].key;
159         }
160         Lock writerLock = context.ctxLock.writeLock();
161         writerLock.lock();
162         try {
163             SSLContext.clearOptions(context.ctx, SSL.SSL_OP_NO_TICKET);
164             if (ticketKeys.length > 0) {
165                 SSLContext.setSessionTicketKeys(context.ctx, ticketKeys);
166             }
167         } finally {
168             writerLock.unlock();
169         }
170     }
171 
172     /**
173      * Enable or disable caching of SSL sessions.
174      */
175     public void setSessionCacheEnabled(boolean enabled) {
176         long mode = enabled ? mask | SSL.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP |
177                 SSL.SSL_SESS_CACHE_NO_INTERNAL_STORE : SSL.SSL_SESS_CACHE_OFF;
178         Lock writerLock = context.ctxLock.writeLock();
179         writerLock.lock();
180         try {
181             SSLContext.setSessionCacheMode(context.ctx, mode);
182             if (!enabled) {
183                 sessionCache.clear();
184             }
185         } finally {
186             writerLock.unlock();
187         }
188     }
189 
190     /**
191      * Return {@code true} if caching of SSL sessions is enabled, {@code false} otherwise.
192      */
193     public boolean isSessionCacheEnabled() {
194         Lock readerLock = context.ctxLock.readLock();
195         readerLock.lock();
196         try {
197             return (SSLContext.getSessionCacheMode(context.ctx) & mask) != 0;
198         } finally {
199             readerLock.unlock();
200         }
201     }
202 
203     /**
204      * Returns the stats of this context.
205      */
206     public OpenSslSessionStats stats() {
207         return stats;
208     }
209 
210     /**
211      * Remove the given {@link OpenSslSession} from the cache, and so not re-use it for new connections.
212      */
213     final void removeFromCache(OpenSslSessionId id) {
214         sessionCache.removeSessionWithId(id);
215     }
216 
217     final boolean isInCache(OpenSslSessionId id) {
218         return sessionCache.containsSessionWithId(id);
219     }
220 
221     void setSessionFromCache(String host, int port, long ssl) {
222         sessionCache.setSession(ssl, host, port);
223     }
224 
225     final void destroy() {
226         if (provider != null) {
227             provider.destroy();
228         }
229         sessionCache.clear();
230     }
231 }