1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  package io.netty.handler.ssl;
17  
18  import io.netty.internal.tcnative.SSLSession;
19  import io.netty.internal.tcnative.SSLSessionCache;
20  import io.netty.util.ResourceLeakDetector;
21  import io.netty.util.ResourceLeakDetectorFactory;
22  import io.netty.util.ResourceLeakTracker;
23  import io.netty.util.internal.EmptyArrays;
24  import io.netty.util.internal.SystemPropertyUtil;
25  
26  import javax.security.cert.X509Certificate;
27  import java.security.Principal;
28  import java.security.cert.Certificate;
29  import java.util.ArrayList;
30  import java.util.Iterator;
31  import java.util.LinkedHashMap;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.concurrent.atomic.AtomicInteger;
35  
36  
37  
38  
39  class OpenSslSessionCache implements SSLSessionCache {
40      private static final OpenSslInternalSession[] EMPTY_SESSIONS = new OpenSslInternalSession[0];
41  
42      private static final int DEFAULT_CACHE_SIZE;
43      static {
44          
45          int cacheSize = SystemPropertyUtil.getInt("javax.net.ssl.sessionCacheSize", 20480);
46          if (cacheSize >= 0) {
47              DEFAULT_CACHE_SIZE = cacheSize;
48          } else {
49              DEFAULT_CACHE_SIZE = 20480;
50          }
51      }
52      private final Map<Long, ReferenceCountedOpenSslEngine> engines;
53  
54      private final Map<OpenSslSessionId, NativeSslSession> sessions =
55              new LinkedHashMap<OpenSslSessionId, NativeSslSession>() {
56  
57                  private static final long serialVersionUID = -7773696788135734448L;
58  
59                  @Override
60                  protected boolean removeEldestEntry(Map.Entry<OpenSslSessionId, NativeSslSession> eldest) {
61                      int maxSize = maximumCacheSize.get();
62                      if (maxSize >= 0 && size() > maxSize) {
63                          removeSessionWithId(eldest.getKey());
64                      }
65                      
66                      return false;
67                  }
68              };
69  
70      private final AtomicInteger maximumCacheSize = new AtomicInteger(DEFAULT_CACHE_SIZE);
71  
72      
73      
74      private final AtomicInteger sessionTimeout = new AtomicInteger(300);
75      private int sessionCounter;
76  
77      OpenSslSessionCache(Map<Long, ReferenceCountedOpenSslEngine> engines) {
78          this.engines = engines;
79      }
80  
81      final void setSessionTimeout(int seconds) {
82          int oldTimeout = sessionTimeout.getAndSet(seconds);
83          if (oldTimeout > seconds) {
84              
85              
86              clear();
87          }
88      }
89  
90      final int getSessionTimeout() {
91          return sessionTimeout.get();
92      }
93  
94      
95  
96  
97  
98  
99  
100     protected boolean sessionCreated(NativeSslSession session) {
101         return true;
102     }
103 
104     
105 
106 
107 
108 
109     protected void sessionRemoved(NativeSslSession session) { }
110 
111     final void setSessionCacheSize(int size) {
112         long oldSize = maximumCacheSize.getAndSet(size);
113         if (oldSize > size || size == 0) {
114             
115             clear();
116         }
117     }
118 
119     final int getSessionCacheSize() {
120         return maximumCacheSize.get();
121     }
122 
123     private void expungeInvalidSessions() {
124         if (sessions.isEmpty()) {
125             return;
126         }
127         long now = System.currentTimeMillis();
128         Iterator<Map.Entry<OpenSslSessionId, NativeSslSession>> iterator = sessions.entrySet().iterator();
129         while (iterator.hasNext()) {
130             NativeSslSession session = iterator.next().getValue();
131             
132             
133             
134             if (session.isValid(now)) {
135                 break;
136             }
137             iterator.remove();
138 
139             notifyRemovalAndFree(session);
140         }
141     }
142 
143     @Override
144     public boolean sessionCreated(long ssl, long sslSession) {
145         ReferenceCountedOpenSslEngine engine = engines.get(ssl);
146         if (engine == null) {
147             
148             return false;
149         }
150         OpenSslInternalSession openSslSession = (OpenSslInternalSession) engine.getSession();
151         
152         
153         NativeSslSession session = new NativeSslSession(sslSession, engine.getPeerHost(), engine.getPeerPort(),
154                 getSessionTimeout() * 1000L, openSslSession.keyValueStorage());
155 
156         openSslSession.setSessionDetails(
157                 session.creationTime, session.lastAccessedTime, session.sessionId(), session.keyValueStorage);
158         synchronized (this) {
159             
160             
161             if (++sessionCounter == 255) {
162                 sessionCounter = 0;
163                 expungeInvalidSessions();
164             }
165 
166             if (!sessionCreated(session)) {
167                 
168                 
169                 session.close();
170                 return false;
171             }
172             final NativeSslSession old = sessions.put(session.sessionId(), session);
173             if (old != null) {
174                 notifyRemovalAndFree(old);
175             }
176         }
177         return true;
178     }
179 
180     @Override
181     public final long getSession(long ssl, byte[] sessionId) {
182         OpenSslSessionId id = new OpenSslSessionId(sessionId);
183         final NativeSslSession session;
184         synchronized (this) {
185             session = sessions.get(id);
186             if (session == null) {
187                 return -1;
188             }
189 
190             
191             
192             if (!session.isValid() ||
193                     
194                     
195                     
196                     
197                     !session.upRef()) {
198                 
199                 removeSessionWithId(session.sessionId());
200                 return -1;
201             }
202 
203             
204             if (session.shouldBeSingleUse()) {
205                 
206                 
207                 removeSessionWithId(session.sessionId());
208             }
209         }
210         session.setLastAccessedTime(System.currentTimeMillis());
211         ReferenceCountedOpenSslEngine engine = engines.get(ssl);
212         if (engine != null) {
213             OpenSslInternalSession sslSession = (OpenSslInternalSession) engine.getSession();
214             sslSession.setSessionDetails(session.getCreationTime(),
215                     session.getLastAccessedTime(), session.sessionId(), session.keyValueStorage);
216         }
217 
218         return session.session();
219     }
220 
221     boolean setSession(long ssl, OpenSslInternalSession session, String host, int port) {
222         
223        return false;
224     }
225 
226     
227 
228 
229     final synchronized void removeSessionWithId(OpenSslSessionId id) {
230         NativeSslSession sslSession = sessions.remove(id);
231         if (sslSession != null) {
232             notifyRemovalAndFree(sslSession);
233         }
234     }
235 
236     
237 
238 
239     final synchronized boolean containsSessionWithId(OpenSslSessionId id) {
240         return sessions.containsKey(id);
241     }
242 
243     private void notifyRemovalAndFree(NativeSslSession session) {
244         sessionRemoved(session);
245         session.free();
246     }
247 
248     
249 
250 
251     final synchronized OpenSslInternalSession getSession(OpenSslSessionId id) {
252         NativeSslSession session = sessions.get(id);
253         if (session != null && !session.isValid()) {
254             
255             
256             removeSessionWithId(session.sessionId());
257             return null;
258         }
259         return session;
260     }
261 
262     
263 
264 
265     final List<OpenSslSessionId> getIds() {
266         final OpenSslInternalSession[] sessionsArray;
267         synchronized (this) {
268             sessionsArray = sessions.values().toArray(EMPTY_SESSIONS);
269         }
270         List<OpenSslSessionId> ids = new ArrayList<OpenSslSessionId>(sessionsArray.length);
271         for (OpenSslInternalSession session: sessionsArray) {
272             if (session.isValid()) {
273                 ids.add(session.sessionId());
274             }
275         }
276         return ids;
277     }
278 
279     
280 
281 
282     synchronized void clear() {
283         Iterator<Map.Entry<OpenSslSessionId, NativeSslSession>> iterator = sessions.entrySet().iterator();
284         while (iterator.hasNext()) {
285             NativeSslSession session = iterator.next().getValue();
286             iterator.remove();
287 
288             
289             notifyRemovalAndFree(session);
290         }
291     }
292 
293     
294 
295 
296     static final class NativeSslSession implements OpenSslInternalSession {
297         static final ResourceLeakDetector<NativeSslSession> LEAK_DETECTOR = ResourceLeakDetectorFactory.instance()
298                 .newResourceLeakDetector(NativeSslSession.class);
299         private final ResourceLeakTracker<NativeSslSession> leakTracker;
300 
301         final Map<String, Object> keyValueStorage;
302 
303         private final long session;
304         private final String peerHost;
305         private final int peerPort;
306         private final OpenSslSessionId id;
307         private final long timeout;
308         private final long creationTime = System.currentTimeMillis();
309         private volatile long lastAccessedTime = creationTime;
310         private volatile boolean valid = true;
311         private boolean freed;
312 
313         NativeSslSession(long session, String peerHost, int peerPort, long timeout,
314                          Map<String, Object> keyValueStorage) {
315             this.session = session;
316             this.peerHost = peerHost;
317             this.peerPort = peerPort;
318             this.timeout = timeout;
319             this.id = new OpenSslSessionId(io.netty.internal.tcnative.SSLSession.getSessionId(session));
320             this.keyValueStorage = keyValueStorage;
321             leakTracker = LEAK_DETECTOR.track(this);
322         }
323 
324         @Override
325         public Map<String, Object> keyValueStorage() {
326             return keyValueStorage;
327         }
328 
329         @Override
330         public void prepareHandshake() {
331             throw new UnsupportedOperationException();
332         }
333 
334         @Override
335         public void setSessionDetails(long creationTime, long lastAccessedTime,
336                                       OpenSslSessionId id, Map<String, Object> keyValueStorage) {
337             throw new UnsupportedOperationException();
338         }
339 
340         boolean shouldBeSingleUse() {
341             assert !freed;
342             return SSLSession.shouldBeSingleUse(session);
343         }
344 
345         long session() {
346             assert !freed;
347             return session;
348         }
349 
350         boolean upRef() {
351             assert !freed;
352             return SSLSession.upRef(session);
353         }
354 
355         synchronized void free() {
356             close();
357             SSLSession.free(session);
358         }
359 
360         void close() {
361             assert !freed;
362             freed = true;
363             invalidate();
364             if (leakTracker != null) {
365                 leakTracker.close(this);
366             }
367         }
368 
369         @Override
370         public OpenSslSessionId sessionId() {
371             return id;
372         }
373 
374         boolean isValid(long now) {
375             return creationTime + timeout >= now && valid;
376         }
377 
378         @Override
379         public void setLocalCertificate(Certificate[] localCertificate) {
380             throw new UnsupportedOperationException();
381         }
382 
383         @Override
384         public OpenSslSessionContext getSessionContext() {
385             return null;
386         }
387 
388         @Override
389         public void tryExpandApplicationBufferSize(int packetLengthDataOnly) {
390             throw new UnsupportedOperationException();
391         }
392 
393         @Override
394         public void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
395                                       byte[][] peerCertificateChain, long creationTime, long timeout) {
396             throw new UnsupportedOperationException();
397         }
398 
399         @Override
400         public byte[] getId() {
401             return id.cloneBytes();
402         }
403 
404         @Override
405         public long getCreationTime() {
406             return creationTime;
407         }
408 
409         @Override
410         public void setLastAccessedTime(long time) {
411             lastAccessedTime = time;
412         }
413 
414         @Override
415         public long getLastAccessedTime() {
416             return lastAccessedTime;
417         }
418 
419         @Override
420         public void invalidate() {
421             valid = false;
422         }
423 
424         @Override
425         public boolean isValid() {
426             return isValid(System.currentTimeMillis());
427         }
428 
429         @Override
430         public void putValue(String name, Object value) {
431             throw new UnsupportedOperationException();
432         }
433 
434         @Override
435         public Object getValue(String name) {
436             return null;
437         }
438 
439         @Override
440         public void removeValue(String name) {
441             
442         }
443 
444         @Override
445         public String[] getValueNames() {
446             return EmptyArrays.EMPTY_STRINGS;
447         }
448 
449         @Override
450         public Certificate[] getPeerCertificates() {
451             throw new UnsupportedOperationException();
452         }
453 
454         @Override
455         public boolean hasPeerCertificates() {
456             throw new UnsupportedOperationException();
457         }
458 
459         @Override
460         public Certificate[] getLocalCertificates() {
461             throw new UnsupportedOperationException();
462         }
463 
464         @Override
465         public X509Certificate[] getPeerCertificateChain() {
466             throw new UnsupportedOperationException();
467         }
468 
469         @Override
470         public Principal getPeerPrincipal() {
471             throw new UnsupportedOperationException();
472         }
473 
474         @Override
475         public Principal getLocalPrincipal() {
476             throw new UnsupportedOperationException();
477         }
478 
479         @Override
480         public String getCipherSuite() {
481             return null;
482         }
483 
484         @Override
485         public String getProtocol() {
486             return null;
487         }
488 
489         @Override
490         public String getPeerHost() {
491             return peerHost;
492         }
493 
494         @Override
495         public int getPeerPort() {
496             return peerPort;
497         }
498 
499         @Override
500         public int getPacketBufferSize() {
501             return ReferenceCountedOpenSslEngine.MAX_RECORD_SIZE;
502         }
503 
504         @Override
505         public int getApplicationBufferSize() {
506             return ReferenceCountedOpenSslEngine.MAX_PLAINTEXT_LENGTH;
507         }
508 
509         @Override
510         public int hashCode() {
511             return id.hashCode();
512         }
513 
514         @Override
515         public boolean equals(Object o) {
516             if (this == o) {
517                 return true;
518             }
519             if (!(o instanceof OpenSslInternalSession)) {
520                 return false;
521             }
522             OpenSslInternalSession session1 = (OpenSslInternalSession) o;
523             return id.equals(session1.sessionId());
524         }
525     }
526 }