View Javadoc
1   /*
2    * Copyright 2018 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.netty5.handler.ssl;
17  
18  import io.netty5.util.internal.EmptyArrays;
19  import io.netty5.util.internal.PlatformDependent;
20  import io.netty5.util.internal.logging.InternalLogger;
21  import io.netty5.util.internal.logging.InternalLoggerFactory;
22  
23  import javax.net.ssl.SSLContext;
24  import javax.net.ssl.TrustManager;
25  import javax.net.ssl.X509ExtendedTrustManager;
26  import javax.net.ssl.X509TrustManager;
27  import java.lang.reflect.Field;
28  import java.security.AccessController;
29  import java.security.KeyManagementException;
30  import java.security.NoSuchAlgorithmException;
31  import java.security.NoSuchProviderException;
32  import java.security.PrivilegedAction;
33  import java.security.cert.CertificateException;
34  import java.security.cert.X509Certificate;
35  
36  /**
37   * Utility which allows to wrap {@link X509TrustManager} implementations with the internal implementation used by
38   * {@code SSLContextImpl} that provides extended verification.
39   *
40   * This is really a "hack" until there is an official API as requested on the in
41   * <a href="https://bugs.openjdk.java.net/projects/JDK/issues/JDK-8210843">JDK-8210843</a>.
42   */
43  final class OpenSslX509TrustManagerWrapper {
44      private static final InternalLogger LOGGER = InternalLoggerFactory
45              .getInstance(OpenSslX509TrustManagerWrapper.class);
46      private static final TrustManagerWrapper WRAPPER;
47  
48      static {
49          // By default we will not do any wrapping but just return the passed in manager.
50          TrustManagerWrapper wrapper = manager -> manager;
51  
52          Throwable cause = null;
53          Throwable unsafeCause = PlatformDependent.getUnsafeUnavailabilityCause();
54          if (unsafeCause == null) {
55              SSLContext context;
56              try {
57                  context = newSSLContext();
58                  // Now init with an array that only holds a X509TrustManager. This should be wrapped into an
59                  // AbstractTrustManagerWrapper which will delegate the TrustManager itself but also do extra
60                  // validations.
61                  //
62                  // See:
63                  // - https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/
64                  //          cadea780bc76/src/share/classes/sun/security/ssl/SSLContextImpl.java#l127
65                  context.init(null, new TrustManager[] {
66                          new X509TrustManager() {
67                              @Override
68                              public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
69                                      throws CertificateException {
70                                  throw new CertificateException();
71                              }
72  
73                              @Override
74                              public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
75                                      throws CertificateException {
76                                  throw new CertificateException();
77                              }
78  
79                              @Override
80                              public X509Certificate[] getAcceptedIssuers() {
81                                  return EmptyArrays.EMPTY_X509_CERTIFICATES;
82                              }
83                          }
84                  }, null);
85              } catch (Throwable error) {
86                  context = null;
87                  cause = error;
88              }
89              if (cause != null) {
90                  LOGGER.debug("Unable to access wrapped TrustManager", cause);
91              } else {
92                  final SSLContext finalContext = context;
93                  Object maybeWrapper = AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
94                      try {
95                          Field contextSpiField = SSLContext.class.getDeclaredField("contextSpi");
96                          final long spiOffset = PlatformDependent.objectFieldOffset(contextSpiField);
97                          Object spi = PlatformDependent.getObject(finalContext, spiOffset);
98                          if (spi != null) {
99                              Class<?> clazz = spi.getClass();
100 
101                             // Let's cycle through the whole hierarchy until we find what we are looking for or
102                             // there is nothing left in which case we will not wrap at all.
103                             do {
104                                 try {
105                                     Field trustManagerField = clazz.getDeclaredField("trustManager");
106                                     final long tmOffset = PlatformDependent.objectFieldOffset(trustManagerField);
107                                     Object trustManager = PlatformDependent.getObject(spi, tmOffset);
108                                     if (trustManager instanceof X509ExtendedTrustManager) {
109                                         return new UnsafeTrustManagerWrapper(spiOffset, tmOffset);
110                                     }
111                                 } catch (NoSuchFieldException ignore) {
112                                     // try next
113                                 }
114                                 clazz = clazz.getSuperclass();
115                             } while (clazz != null);
116                         }
117                         throw new NoSuchFieldException();
118                     } catch (NoSuchFieldException | SecurityException e) {
119                         return e;
120                     }
121                 });
122                 if (maybeWrapper instanceof Throwable) {
123                     LOGGER.debug("Unable to access wrapped TrustManager", (Throwable) maybeWrapper);
124                 } else {
125                     wrapper = (TrustManagerWrapper) maybeWrapper;
126                 }
127             }
128         } else {
129             LOGGER.debug("Unable to access wrapped TrustManager", cause);
130         }
131         WRAPPER = wrapper;
132     }
133 
134     private OpenSslX509TrustManagerWrapper() { }
135 
136     static X509TrustManager wrapIfNeeded(X509TrustManager trustManager) {
137         return WRAPPER.wrapIfNeeded(trustManager);
138     }
139 
140     private interface TrustManagerWrapper {
141         X509TrustManager wrapIfNeeded(X509TrustManager manager);
142     }
143 
144     private static SSLContext newSSLContext() throws NoSuchAlgorithmException, NoSuchProviderException {
145         // As this depends on the implementation detail we should explicit select the correct provider.
146         // See https://github.com/netty/netty/issues/10374
147         return SSLContext.getInstance("TLS", "SunJSSE");
148     }
149 
150     private static final class UnsafeTrustManagerWrapper implements TrustManagerWrapper {
151         private final long spiOffset;
152         private final long tmOffset;
153 
154         UnsafeTrustManagerWrapper(long spiOffset, long tmOffset) {
155             this.spiOffset = spiOffset;
156             this.tmOffset = tmOffset;
157         }
158 
159         @Override
160         public X509TrustManager wrapIfNeeded(X509TrustManager manager) {
161             if (!(manager instanceof X509ExtendedTrustManager)) {
162                 try {
163                     SSLContext ctx = newSSLContext();
164                     ctx.init(null, new TrustManager[] { manager }, null);
165                     Object spi = PlatformDependent.getObject(ctx, spiOffset);
166                     if (spi != null) {
167                         Object tm = PlatformDependent.getObject(spi, tmOffset);
168                         if (tm instanceof X509ExtendedTrustManager) {
169                             return (X509TrustManager) tm;
170                         }
171                     }
172                 } catch (NoSuchAlgorithmException | KeyManagementException | NoSuchProviderException e) {
173                     // This should never happen as we did the same in the static block
174                     // before.
175                     PlatformDependent.throwException(e);
176                 }
177             }
178             return manager;
179         }
180     }
181 }