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 io.netty.util.internal.ObjectUtil;
19  import io.netty.util.internal.PlatformDependent;
20  import io.netty.util.internal.SuppressJava6Requirement;
21  
22  import java.net.Socket;
23  import java.security.cert.Certificate;
24  import java.security.cert.CertificateException;
25  import java.security.cert.X509Certificate;
26  import java.util.Collections;
27  import java.util.Set;
28  import java.util.WeakHashMap;
29  import java.util.concurrent.atomic.AtomicReference;
30  import javax.net.ssl.SSLEngine;
31  import javax.net.ssl.SSLPeerUnverifiedException;
32  import javax.net.ssl.TrustManager;
33  import javax.net.ssl.X509ExtendedTrustManager;
34  
35  final class ResumptionController {
36      private final Set<SSLEngine> confirmedValidations;
37      private final AtomicReference<ResumableX509ExtendedTrustManager> resumableTm;
38  
39      ResumptionController() {
40          confirmedValidations = Collections.synchronizedSet(
41                  Collections.newSetFromMap(new WeakHashMap<SSLEngine, Boolean>()));
42          resumableTm = new AtomicReference<ResumableX509ExtendedTrustManager>();
43      }
44  
45      @SuppressJava6Requirement(reason = "Guarded by version check")
46      public TrustManager wrapIfNeeded(TrustManager tm) {
47          if (tm instanceof ResumableX509ExtendedTrustManager) {
48              if (PlatformDependent.javaVersion() < 7 || !(tm instanceof X509ExtendedTrustManager)) {
49                  throw new IllegalStateException("ResumableX509ExtendedTrustManager implementation must be a " +
50                          "subclass of X509ExtendedTrustManager, found: " + (tm == null ? null : tm.getClass()));
51              }
52              if (!resumableTm.compareAndSet(null, (ResumableX509ExtendedTrustManager) tm)) {
53                  throw new IllegalStateException(
54                          "Only one ResumableX509ExtendedTrustManager can be configured for resumed sessions");
55              }
56              return new X509ExtendedWrapTrustManager((X509ExtendedTrustManager) tm, confirmedValidations);
57          }
58          return tm;
59      }
60  
61      public void remove(SSLEngine engine) {
62          if (resumableTm.get() != null) {
63              confirmedValidations.remove(unwrapEngine(engine));
64          }
65      }
66  
67      public boolean validateResumeIfNeeded(SSLEngine engine)
68              throws CertificateException, SSLPeerUnverifiedException {
69          ResumableX509ExtendedTrustManager tm;
70          boolean valid = engine.getSession().isValid();
71          if (valid && (tm = resumableTm.get()) != null) {
72              Certificate[] peerCertificates = engine.getSession().getPeerCertificates();
73  
74              // Unwrap JdkSslEngines because they add their inner JDK SSLEngine objects to the set.
75              engine = unwrapEngine(engine);
76  
77              if (!confirmedValidations.remove(engine)) {
78                  // This is a resumed session.
79                  if (engine.getUseClientMode()) {
80                      // We are the client, resuming a session trusting the server
81                      tm.resumeServerTrusted(chainOf(peerCertificates), engine);
82                  } else {
83                      // We are the server, resuming a session trusting the client
84                      tm.resumeClientTrusted(chainOf(peerCertificates), engine);
85                  }
86                  return true;
87              }
88          }
89          return false;
90      }
91  
92      private static SSLEngine unwrapEngine(SSLEngine engine) {
93          if (engine instanceof JdkSslEngine) {
94              return ((JdkSslEngine) engine).getWrappedEngine();
95          }
96          return engine;
97      }
98  
99      private static X509Certificate[] chainOf(Certificate[] peerCertificates) {
100         if (peerCertificates instanceof X509Certificate[]) {
101             //noinspection SuspiciousArrayCast
102             return (X509Certificate[]) peerCertificates;
103         }
104         X509Certificate[] chain = new X509Certificate[peerCertificates.length];
105         for (int i = 0; i < peerCertificates.length; i++) {
106             Certificate cert = peerCertificates[i];
107             if (cert instanceof X509Certificate || cert == null) {
108                 chain[i] = (X509Certificate) cert;
109             } else {
110                 throw new IllegalArgumentException("Only X509Certificates are supported, found: " + cert.getClass());
111             }
112         }
113         return chain;
114     }
115 
116     @SuppressJava6Requirement(reason = "Guarded by version check")
117     private static final class X509ExtendedWrapTrustManager extends X509ExtendedTrustManager {
118         private final X509ExtendedTrustManager trustManager;
119         private final Set<SSLEngine> confirmedValidations;
120 
121         X509ExtendedWrapTrustManager(X509ExtendedTrustManager trustManager, Set<SSLEngine> confirmedValidations) {
122             this.trustManager = trustManager;
123             this.confirmedValidations = confirmedValidations;
124         }
125 
126         private static void unsupported() throws CertificateException {
127             throw new CertificateException(
128                     new UnsupportedOperationException("Resumable trust managers require the SSLEngine parameter"));
129         }
130 
131         @Override
132         public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
133                 throws CertificateException {
134             unsupported();
135         }
136 
137         @Override
138         public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
139                 throws CertificateException {
140             unsupported();
141         }
142 
143         @Override
144         public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
145             unsupported();
146         }
147 
148         @Override
149         public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
150             unsupported();
151         }
152 
153         @Override
154         public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
155                 throws CertificateException {
156             trustManager.checkClientTrusted(chain, authType, engine);
157             confirmedValidations.add(engine);
158         }
159 
160         @Override
161         public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
162                 throws CertificateException {
163             trustManager.checkServerTrusted(chain, authType, engine);
164             confirmedValidations.add(engine);
165         }
166 
167         @Override
168         public X509Certificate[] getAcceptedIssuers() {
169             return trustManager.getAcceptedIssuers();
170         }
171     }
172 }