View Javadoc
1   /*
2    * Copyright 2021 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.util.AsciiString;
20  
21  import java.util.HashMap;
22  import java.util.Map;
23  
24  /**
25   * {@link OpenSslSessionCache} that is used by the client-side.
26   */
27  final class OpenSslClientSessionCache extends OpenSslSessionCache {
28      // TODO: Should we support to have a List of OpenSslSessions for a Host/Port key and so be able to
29      // support sessions for different protocols / ciphers to the same remote peer ?
30      private final Map<HostPort, NativeSslSession> sessions = new HashMap<HostPort, NativeSslSession>();
31  
32      OpenSslClientSessionCache(OpenSslEngineMap engineMap) {
33          super(engineMap);
34      }
35  
36      @Override
37      protected boolean sessionCreated(NativeSslSession session) {
38          assert Thread.holdsLock(this);
39          HostPort hostPort = keyFor(session.getPeerHost(), session.getPeerPort());
40          if (hostPort == null || sessions.containsKey(hostPort)) {
41              return false;
42          }
43          sessions.put(hostPort, session);
44          return true;
45      }
46  
47      @Override
48      protected void sessionRemoved(NativeSslSession session) {
49          assert Thread.holdsLock(this);
50          HostPort hostPort = keyFor(session.getPeerHost(), session.getPeerPort());
51          if (hostPort == null) {
52              return;
53          }
54          sessions.remove(hostPort);
55      }
56  
57      @Override
58      boolean setSession(long ssl, OpenSslSession session, String host, int port) {
59          HostPort hostPort = keyFor(host, port);
60          if (hostPort == null) {
61              return false;
62          }
63          final NativeSslSession nativeSslSession;
64          final boolean reused;
65          boolean singleUsed = false;
66          synchronized (this) {
67              nativeSslSession = sessions.get(hostPort);
68              if (nativeSslSession == null) {
69                  return false;
70              }
71              if (!nativeSslSession.isValid()) {
72                  removeSessionWithId(nativeSslSession.sessionId());
73                  return false;
74              }
75              // Try to set the session, if true is returned OpenSSL incremented the reference count
76              // of the underlying SSL_SESSION*.
77              reused = SSL.setSession(ssl, nativeSslSession.session());
78              if (reused) {
79                  singleUsed = nativeSslSession.shouldBeSingleUse();
80              }
81          }
82  
83          if (reused) {
84              if (singleUsed) {
85                  // Should only be used once
86                  nativeSslSession.invalidate();
87                  session.invalidate();
88              }
89              nativeSslSession.setLastAccessedTime(System.currentTimeMillis());
90              session.setSessionDetails(nativeSslSession.getCreationTime(), nativeSslSession.getLastAccessedTime(),
91                      nativeSslSession.sessionId(), nativeSslSession.keyValueStorage);
92          }
93          return reused;
94      }
95  
96      private static HostPort keyFor(String host, int port) {
97          if (host == null && port < 1) {
98              return null;
99          }
100         return new HostPort(host, port);
101     }
102 
103     @Override
104     synchronized void clear() {
105         super.clear();
106         sessions.clear();
107     }
108 
109     /**
110      * Host / Port tuple used to find a {@link OpenSslSession} in the cache.
111      */
112     private static final class HostPort {
113         private final int hash;
114         private final String host;
115         private final int port;
116 
117         HostPort(String host, int port) {
118             this.host = host;
119             this.port = port;
120             // Calculate a hashCode that does ignore case.
121             this.hash = 31 * AsciiString.hashCode(host) + port;
122         }
123 
124         @Override
125         public int hashCode() {
126             return hash;
127         }
128 
129         @Override
130         public boolean equals(Object obj) {
131             if (!(obj instanceof HostPort)) {
132                 return false;
133             }
134             HostPort other = (HostPort) obj;
135             return port == other.port && host.equalsIgnoreCase(other.host);
136         }
137 
138         @Override
139         public String toString() {
140             return "HostPort{" +
141                     "host='" + host + '\'' +
142                     ", port=" + port +
143                     '}';
144         }
145     }
146 }