View Javadoc

1   /*
2    * Copyright 2016 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    *   http://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.logging.InternalLogger;
19  import io.netty.util.internal.logging.InternalLoggerFactory;
20  import io.netty.internal.tcnative.CertificateRequestedCallback;
21  import io.netty.internal.tcnative.SSL;
22  import io.netty.internal.tcnative.SSLContext;
23  
24  import java.security.KeyStore;
25  import java.security.PrivateKey;
26  import java.security.cert.X509Certificate;
27  import java.util.HashSet;
28  import java.util.Set;
29  
30  import javax.net.ssl.KeyManagerFactory;
31  import javax.net.ssl.SSLException;
32  import javax.net.ssl.SSLHandshakeException;
33  import javax.net.ssl.TrustManagerFactory;
34  import javax.net.ssl.X509ExtendedKeyManager;
35  import javax.net.ssl.X509ExtendedTrustManager;
36  import javax.net.ssl.X509KeyManager;
37  import javax.net.ssl.X509TrustManager;
38  import javax.security.auth.x500.X500Principal;
39  
40  /**
41   * A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
42   * <p>Instances of this class must be {@link #release() released} or else native memory will leak!
43   *
44   * <p>Instances of this class <strong>must not</strong> be released before any {@link ReferenceCountedOpenSslEngine}
45   * which depends upon the instance of this class is released. Otherwise if any method of
46   * {@link ReferenceCountedOpenSslEngine} is called which uses this class's JNI resources the JVM may crash.
47   */
48  public final class ReferenceCountedOpenSslClientContext extends ReferenceCountedOpenSslContext {
49      private static final InternalLogger logger =
50              InternalLoggerFactory.getInstance(ReferenceCountedOpenSslClientContext.class);
51      private final OpenSslSessionContext sessionContext;
52  
53      ReferenceCountedOpenSslClientContext(X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
54                                           X509Certificate[] keyCertChain, PrivateKey key, String keyPassword,
55                                           KeyManagerFactory keyManagerFactory, Iterable<String> ciphers,
56                                           CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
57                                           String[] protocols, long sessionCacheSize, long sessionTimeout,
58                                           boolean enableOcsp) throws SSLException {
59          super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT, keyCertChain,
60                ClientAuth.NONE, protocols, false, enableOcsp, true);
61          boolean success = false;
62          try {
63              sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
64                                                 keyCertChain, key, keyPassword, keyManagerFactory);
65              success = true;
66          } finally {
67              if (!success) {
68                  release();
69              }
70          }
71      }
72  
73      @Override
74      OpenSslKeyMaterialManager keyMaterialManager() {
75          return null;
76      }
77  
78      @Override
79      public OpenSslSessionContext sessionContext() {
80          return sessionContext;
81      }
82  
83      static OpenSslSessionContext newSessionContext(ReferenceCountedOpenSslContext thiz, long ctx,
84                                                     OpenSslEngineMap engineMap,
85                                                     X509Certificate[] trustCertCollection,
86                                                     TrustManagerFactory trustManagerFactory,
87                                                     X509Certificate[] keyCertChain, PrivateKey key, String keyPassword,
88                                                     KeyManagerFactory keyManagerFactory) throws SSLException {
89          if (key == null && keyCertChain != null || key != null && keyCertChain == null) {
90              throw new IllegalArgumentException(
91                      "Either both keyCertChain and key needs to be null or none of them");
92          }
93          try {
94              if (!OpenSsl.useKeyManagerFactory()) {
95                  if (keyManagerFactory != null) {
96                      throw new IllegalArgumentException(
97                              "KeyManagerFactory not supported");
98                  }
99                  if (keyCertChain != null/* && key != null*/) {
100                     setKeyMaterial(ctx, keyCertChain, key, keyPassword);
101                 }
102             } else {
103                 // javadocs state that keyManagerFactory has precedent over keyCertChain
104                 if (keyManagerFactory == null && keyCertChain != null) {
105                     keyManagerFactory = buildKeyManagerFactory(
106                             keyCertChain, key, keyPassword, keyManagerFactory);
107                 }
108 
109                 if (keyManagerFactory != null) {
110                     X509KeyManager keyManager = chooseX509KeyManager(keyManagerFactory.getKeyManagers());
111                     OpenSslKeyMaterialManager materialManager = useExtendedKeyManager(keyManager) ?
112                             new OpenSslExtendedKeyMaterialManager(
113                                     (X509ExtendedKeyManager) keyManager, keyPassword) :
114                             new OpenSslKeyMaterialManager(keyManager, keyPassword);
115                     SSLContext.setCertRequestedCallback(ctx, new OpenSslCertificateRequestedCallback(
116                             engineMap, materialManager));
117                 }
118             }
119         } catch (Exception e) {
120             throw new SSLException("failed to set certificate and key", e);
121         }
122 
123         SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
124 
125         try {
126             if (trustCertCollection != null) {
127                 trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory);
128             } else if (trustManagerFactory == null) {
129                 trustManagerFactory = TrustManagerFactory.getInstance(
130                         TrustManagerFactory.getDefaultAlgorithm());
131                 trustManagerFactory.init((KeyStore) null);
132             }
133             final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
134 
135             // IMPORTANT: The callbacks set for verification must be static to prevent memory leak as
136             //            otherwise the context can never be collected. This is because the JNI code holds
137             //            a global reference to the callbacks.
138             //
139             //            See https://github.com/netty/netty/issues/5372
140 
141             // Use this to prevent an error when running on java < 7
142             if (useExtendedTrustManager(manager)) {
143                 SSLContext.setCertVerifyCallback(ctx,
144                         new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager));
145             } else {
146                 SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager));
147             }
148         } catch (Exception e) {
149             throw new SSLException("unable to setup trustmanager", e);
150         }
151         return new OpenSslClientSessionContext(thiz);
152     }
153 
154     // No cache is currently supported for client side mode.
155     static final class OpenSslClientSessionContext extends OpenSslSessionContext {
156         OpenSslClientSessionContext(ReferenceCountedOpenSslContext context) {
157             super(context);
158         }
159 
160         @Override
161         public void setSessionTimeout(int seconds) {
162             if (seconds < 0) {
163                 throw new IllegalArgumentException();
164             }
165         }
166 
167         @Override
168         public int getSessionTimeout() {
169             return 0;
170         }
171 
172         @Override
173         public void setSessionCacheSize(int size)  {
174             if (size < 0) {
175                 throw new IllegalArgumentException();
176             }
177         }
178 
179         @Override
180         public int getSessionCacheSize() {
181             return 0;
182         }
183 
184         @Override
185         public void setSessionCacheEnabled(boolean enabled) {
186             // ignored
187         }
188 
189         @Override
190         public boolean isSessionCacheEnabled() {
191             return false;
192         }
193     }
194 
195     private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier {
196         private final X509TrustManager manager;
197 
198         TrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509TrustManager manager) {
199             super(engineMap);
200             this.manager = manager;
201         }
202 
203         @Override
204         void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth)
205                 throws Exception {
206             manager.checkServerTrusted(peerCerts, auth);
207         }
208     }
209 
210     private static final class ExtendedTrustManagerVerifyCallback extends AbstractCertificateVerifier {
211         private final X509ExtendedTrustManager manager;
212 
213         ExtendedTrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509ExtendedTrustManager manager) {
214             super(engineMap);
215             this.manager = manager;
216         }
217 
218         @Override
219         void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth)
220                 throws Exception {
221             manager.checkServerTrusted(peerCerts, auth, engine);
222         }
223     }
224 
225     private static final class OpenSslCertificateRequestedCallback implements CertificateRequestedCallback {
226         private final OpenSslEngineMap engineMap;
227         private final OpenSslKeyMaterialManager keyManagerHolder;
228 
229         OpenSslCertificateRequestedCallback(OpenSslEngineMap engineMap, OpenSslKeyMaterialManager keyManagerHolder) {
230             this.engineMap = engineMap;
231             this.keyManagerHolder = keyManagerHolder;
232         }
233 
234         @Override
235         public KeyMaterial requested(long ssl, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals) {
236             final ReferenceCountedOpenSslEngine engine = engineMap.get(ssl);
237             try {
238                 final Set<String> keyTypesSet = supportedClientKeyTypes(keyTypeBytes);
239                 final String[] keyTypes = keyTypesSet.toArray(new String[keyTypesSet.size()]);
240                 final X500Principal[] issuers;
241                 if (asn1DerEncodedPrincipals == null) {
242                     issuers = null;
243                 } else {
244                     issuers = new X500Principal[asn1DerEncodedPrincipals.length];
245                     for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) {
246                         issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
247                     }
248                 }
249                 return keyManagerHolder.keyMaterial(engine, keyTypes, issuers);
250             } catch (Throwable cause) {
251                 logger.debug("request of key failed", cause);
252                 SSLHandshakeException e = new SSLHandshakeException("General OpenSslEngine problem");
253                 e.initCause(cause);
254                 engine.handshakeException = e;
255                 return null;
256             }
257         }
258 
259         /**
260          * Gets the supported key types for client certificates.
261          *
262          * @param clientCertificateTypes {@code ClientCertificateType} values provided by the server.
263          *        See https://www.ietf.org/assignments/tls-parameters/tls-parameters.xml.
264          * @return supported key types that can be used in {@code X509KeyManager.chooseClientAlias} and
265          *         {@code X509ExtendedKeyManager.chooseEngineClientAlias}.
266          */
267         private static Set<String> supportedClientKeyTypes(byte[] clientCertificateTypes) {
268             Set<String> result = new HashSet<String>(clientCertificateTypes.length);
269             for (byte keyTypeCode : clientCertificateTypes) {
270                 String keyType = clientKeyType(keyTypeCode);
271                 if (keyType == null) {
272                     // Unsupported client key type -- ignore
273                     continue;
274                 }
275                 result.add(keyType);
276             }
277             return result;
278         }
279 
280         private static String clientKeyType(byte clientCertificateType) {
281             // See also http://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
282             switch (clientCertificateType) {
283                 case CertificateRequestedCallback.TLS_CT_RSA_SIGN:
284                     return OpenSslKeyMaterialManager.KEY_TYPE_RSA; // RFC rsa_sign
285                 case CertificateRequestedCallback.TLS_CT_RSA_FIXED_DH:
286                     return OpenSslKeyMaterialManager.KEY_TYPE_DH_RSA; // RFC rsa_fixed_dh
287                 case CertificateRequestedCallback.TLS_CT_ECDSA_SIGN:
288                     return OpenSslKeyMaterialManager.KEY_TYPE_EC; // RFC ecdsa_sign
289                 case CertificateRequestedCallback.TLS_CT_RSA_FIXED_ECDH:
290                     return OpenSslKeyMaterialManager.KEY_TYPE_EC_RSA; // RFC rsa_fixed_ecdh
291                 case CertificateRequestedCallback.TLS_CT_ECDSA_FIXED_ECDH:
292                     return OpenSslKeyMaterialManager.KEY_TYPE_EC_EC; // RFC ecdsa_fixed_ecdh
293                 default:
294                     return null;
295             }
296         }
297     }
298 }