View Javadoc
1   /*
2    * Copyright 2024 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 java.net.Socket;
19  import java.security.cert.Certificate;
20  import java.security.cert.CertificateException;
21  import java.security.cert.X509Certificate;
22  import java.util.Collections;
23  import java.util.Set;
24  import java.util.WeakHashMap;
25  import java.util.concurrent.atomic.AtomicReference;
26  import javax.net.ssl.SSLEngine;
27  import javax.net.ssl.SSLPeerUnverifiedException;
28  import javax.net.ssl.SSLSession;
29  import javax.net.ssl.TrustManager;
30  import javax.net.ssl.X509ExtendedTrustManager;
31  
32  final class ResumptionController {
33      private final Set<SSLEngine> confirmedValidations;
34      private final AtomicReference<ResumableX509ExtendedTrustManager> resumableTm;
35  
36      ResumptionController() {
37          confirmedValidations = Collections.synchronizedSet(
38                  Collections.newSetFromMap(new WeakHashMap<SSLEngine, Boolean>()));
39          resumableTm = new AtomicReference<ResumableX509ExtendedTrustManager>();
40      }
41  
42      public TrustManager wrapIfNeeded(TrustManager tm) {
43          if (tm instanceof ResumableX509ExtendedTrustManager) {
44              if (!(tm instanceof X509ExtendedTrustManager)) {
45                  throw new IllegalStateException("ResumableX509ExtendedTrustManager implementation must be a " +
46                          "subclass of X509ExtendedTrustManager, found: " + (tm == null ? null : tm.getClass()));
47              }
48              if (!resumableTm.compareAndSet(null, (ResumableX509ExtendedTrustManager) tm)) {
49                  throw new IllegalStateException(
50                          "Only one ResumableX509ExtendedTrustManager can be configured for resumed sessions");
51              }
52              return new X509ExtendedWrapTrustManager((X509ExtendedTrustManager) tm, confirmedValidations);
53          }
54          return tm;
55      }
56  
57      public void remove(SSLEngine engine) {
58          if (resumableTm.get() != null) {
59              confirmedValidations.remove(unwrapEngine(engine));
60          }
61      }
62  
63      public boolean validateResumeIfNeeded(SSLEngine engine)
64              throws CertificateException, SSLPeerUnverifiedException {
65          ResumableX509ExtendedTrustManager tm;
66          SSLSession session = engine.getSession();
67          boolean valid = session.isValid();
68  
69          // Look for resumption if the session is valid, and we expect to authenticate our peer:
70          //   1.   Clients always authenticate the server.
71          //   2.a. Servers only authenticate the client if they need auth,
72          //   2.b. or if they requested auth and the client provided.
73          //
74          // If a server only "want" but don't "need" auth (ClientAuth.OPTIONAL) and the client didn't provide
75          // any certificates, then `session.getPeerCertificates()` will throw `SSLPeerUnverifiedException`.
76          if (valid && (engine.getUseClientMode() || engine.getNeedClientAuth() || engine.getWantClientAuth()) &&
77                  (tm = resumableTm.get()) != null) {
78              // Unwrap JdkSslEngines because they add their inner JDK SSLEngine objects to the set.
79              engine = unwrapEngine(engine);
80  
81              if (!confirmedValidations.remove(engine)) {
82                  Certificate[] peerCertificates;
83                  try {
84                      peerCertificates = session.getPeerCertificates();
85                  } catch (SSLPeerUnverifiedException e) {
86                      if (engine.getUseClientMode() || engine.getNeedClientAuth()) {
87                          // Auth is required, and we got none.
88                          throw e;
89                      }
90                      // Auth is optional, and none were provided. Skip out; session resumed but nothing to authenticate.
91                      return false;
92                  }
93  
94                  // This is a resumed session.
95                  if (engine.getUseClientMode()) {
96                      // We are the client, resuming a session trusting the server
97                      tm.resumeServerTrusted(chainOf(peerCertificates), engine);
98                  } else {
99                      // We are the server, resuming a session trusting the client
100                     tm.resumeClientTrusted(chainOf(peerCertificates), engine);
101                 }
102                 return true;
103             }
104         }
105         return false;
106     }
107 
108     private static SSLEngine unwrapEngine(SSLEngine engine) {
109         if (engine instanceof JdkSslEngine) {
110             return ((JdkSslEngine) engine).getWrappedEngine();
111         }
112         return engine;
113     }
114 
115     private static X509Certificate[] chainOf(Certificate[] peerCertificates) {
116         if (peerCertificates instanceof X509Certificate[]) {
117             //noinspection SuspiciousArrayCast
118             return (X509Certificate[]) peerCertificates;
119         }
120         X509Certificate[] chain = new X509Certificate[peerCertificates.length];
121         for (int i = 0; i < peerCertificates.length; i++) {
122             Certificate cert = peerCertificates[i];
123             if (cert instanceof X509Certificate || cert == null) {
124                 chain[i] = (X509Certificate) cert;
125             } else {
126                 throw new IllegalArgumentException("Only X509Certificates are supported, found: " + cert.getClass());
127             }
128         }
129         return chain;
130     }
131 
132     private static final class X509ExtendedWrapTrustManager extends X509ExtendedTrustManager {
133         private final X509ExtendedTrustManager trustManager;
134         private final Set<SSLEngine> confirmedValidations;
135 
136         X509ExtendedWrapTrustManager(X509ExtendedTrustManager trustManager, Set<SSLEngine> confirmedValidations) {
137             this.trustManager = trustManager;
138             this.confirmedValidations = confirmedValidations;
139         }
140 
141         private static void unsupported() throws CertificateException {
142             throw new CertificateException(
143                     new UnsupportedOperationException("Resumable trust managers require the SSLEngine parameter"));
144         }
145 
146         @Override
147         public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
148                 throws CertificateException {
149             unsupported();
150         }
151 
152         @Override
153         public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
154                 throws CertificateException {
155             unsupported();
156         }
157 
158         @Override
159         public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
160             unsupported();
161         }
162 
163         @Override
164         public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
165             unsupported();
166         }
167 
168         @Override
169         public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
170                 throws CertificateException {
171             trustManager.checkClientTrusted(chain, authType, engine);
172             confirmedValidations.add(engine);
173         }
174 
175         @Override
176         public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
177                 throws CertificateException {
178             trustManager.checkServerTrusted(chain, authType, engine);
179             confirmedValidations.add(engine);
180         }
181 
182         @Override
183         public X509Certificate[] getAcceptedIssuers() {
184             return trustManager.getAcceptedIssuers();
185         }
186     }
187 }