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