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