View Javadoc
1   /*
2    * Copyright 2014 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.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufInputStream;
20  import io.netty.util.internal.logging.InternalLogger;
21  import io.netty.util.internal.logging.InternalLoggerFactory;
22  import org.apache.tomcat.jni.CertificateVerifier;
23  import org.apache.tomcat.jni.SSL;
24  import org.apache.tomcat.jni.SSLContext;
25  
26  import javax.net.ssl.SSLException;
27  import javax.net.ssl.TrustManagerFactory;
28  import javax.net.ssl.X509TrustManager;
29  import java.io.File;
30  import java.security.KeyFactory;
31  import java.security.KeyStore;
32  import java.security.PrivateKey;
33  import java.security.cert.Certificate;
34  import java.security.cert.CertificateFactory;
35  import java.security.cert.X509Certificate;
36  import java.security.spec.InvalidKeySpecException;
37  import java.security.spec.PKCS8EncodedKeySpec;
38  import java.util.ArrayList;
39  import java.util.List;
40  
41  import static io.netty.util.internal.ObjectUtil.*;
42  
43  /**
44   * A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
45   */
46  public final class OpenSslServerContext extends OpenSslContext {
47      private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslServerContext.class);
48  
49      private final OpenSslServerSessionContext sessionContext;
50  
51      /**
52       * Creates a new instance.
53       *
54       * @param certChainFile an X.509 certificate chain file in PEM format
55       * @param keyFile a PKCS#8 private key file in PEM format
56       */
57      public OpenSslServerContext(File certChainFile, File keyFile) throws SSLException {
58          this(certChainFile, keyFile, null);
59      }
60  
61      /**
62       * Creates a new instance.
63       *
64       * @param certChainFile an X.509 certificate chain file in PEM format
65       * @param keyFile a PKCS#8 private key file in PEM format
66       * @param keyPassword the password of the {@code keyFile}.
67       *                    {@code null} if it's not password-protected.
68       */
69      public OpenSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException {
70          this(certChainFile, keyFile, keyPassword, null, null,
71              OpenSslDefaultApplicationProtocolNegotiator.INSTANCE, 0, 0);
72      }
73  
74      /**
75       * Creates a new instance.
76       *
77       * @param certChainFile an X.509 certificate chain file in PEM format
78       * @param keyFile a PKCS#8 private key file in PEM format
79       * @param keyPassword the password of the {@code keyFile}.
80       *                    {@code null} if it's not password-protected.
81       * @param ciphers the cipher suites to enable, in the order of preference.
82       *                {@code null} to use the default cipher suites.
83       * @param apn Provides a means to configure parameters related to application protocol negotiation.
84       * @param sessionCacheSize the size of the cache used for storing SSL session objects.
85       *                         {@code 0} to use the default value.
86       * @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
87       *                       {@code 0} to use the default value.
88       */
89      public OpenSslServerContext(
90              File certChainFile, File keyFile, String keyPassword,
91              Iterable<String> ciphers, ApplicationProtocolConfig apn,
92              long sessionCacheSize, long sessionTimeout) throws SSLException {
93          this(certChainFile, keyFile, keyPassword, null, ciphers,
94              toNegotiator(apn, false), sessionCacheSize, sessionTimeout);
95      }
96  
97      /**
98       * Creates a new instance.
99       *
100      * @param certChainFile an X.509 certificate chain file in PEM format
101      * @param keyFile a PKCS#8 private key file in PEM format
102      * @param keyPassword the password of the {@code keyFile}.
103      *                    {@code null} if it's not password-protected.
104      * @param ciphers the cipher suites to enable, in the order of preference.
105      *                {@code null} to use the default cipher suites.
106      * @param config Application protocol config.
107      * @param sessionCacheSize the size of the cache used for storing SSL session objects.
108      *                         {@code 0} to use the default value.
109      * @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
110      *                       {@code 0} to use the default value.
111      */
112     public OpenSslServerContext(
113             File certChainFile, File keyFile, String keyPassword, TrustManagerFactory trustManagerFactory,
114             Iterable<String> ciphers, ApplicationProtocolConfig config,
115             long sessionCacheSize, long sessionTimeout) throws SSLException {
116         this(certChainFile, keyFile, keyPassword, trustManagerFactory, ciphers,
117                 toNegotiator(config, true), sessionCacheSize, sessionTimeout);
118     }
119 
120     /**
121      * Creates a new instance.
122      *
123      * @param certChainFile an X.509 certificate chain file in PEM format
124      * @param keyFile a PKCS#8 private key file in PEM format
125      * @param keyPassword the password of the {@code keyFile}.
126      *                    {@code null} if it's not password-protected.
127      * @param ciphers the cipher suites to enable, in the order of preference.
128      *                {@code null} to use the default cipher suites.
129      * @param apn Application protocol negotiator.
130      * @param sessionCacheSize the size of the cache used for storing SSL session objects.
131      *                         {@code 0} to use the default value.
132      * @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
133      *                       {@code 0} to use the default value.
134      */
135     public OpenSslServerContext(
136             File certChainFile, File keyFile, String keyPassword, TrustManagerFactory trustManagerFactory,
137             Iterable<String> ciphers, OpenSslApplicationProtocolNegotiator apn,
138             long sessionCacheSize, long sessionTimeout) throws SSLException {
139 
140          super(ciphers, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER);
141          OpenSsl.ensureAvailability();
142 
143         checkNotNull(certChainFile, "certChainFile");
144         if (!certChainFile.isFile()) {
145             throw new IllegalArgumentException("certChainFile is not a file: " + certChainFile);
146         }
147         checkNotNull(keyFile, "keyFile");
148         if (!keyFile.isFile()) {
149             throw new IllegalArgumentException("keyPath is not a file: " + keyFile);
150         }
151         if (keyPassword == null) {
152             keyPassword = "";
153         }
154 
155         // Create a new SSL_CTX and configure it.
156         boolean success = false;
157         try {
158             synchronized (OpenSslContext.class) {
159                 /* Set certificate verification policy. */
160                 SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
161 
162                 /* Load the certificate chain. We must skip the first cert when server mode */
163                 if (!SSLContext.setCertificateChainFile(ctx, certChainFile.getPath(), true)) {
164                     long error = SSL.getLastErrorNumber();
165                     if (OpenSsl.isError(error)) {
166                         String err = SSL.getErrorString(error);
167                         throw new SSLException(
168                                 "failed to set certificate chain: " + certChainFile + " (" + err + ')');
169                     }
170                 }
171 
172                 /* Load the certificate file and private key. */
173                 try {
174                     if (!SSLContext.setCertificate(
175                             ctx, certChainFile.getPath(), keyFile.getPath(), keyPassword, SSL.SSL_AIDX_RSA)) {
176                         long error = SSL.getLastErrorNumber();
177                         if (OpenSsl.isError(error)) {
178                             String err = SSL.getErrorString(error);
179                             throw new SSLException("failed to set certificate: " +
180                                     certChainFile + " and " + keyFile + " (" + err + ')');
181                         }
182                     }
183                 } catch (SSLException e) {
184                     throw e;
185                 } catch (Exception e) {
186                     throw new SSLException("failed to set certificate: " + certChainFile + " and " + keyFile, e);
187                 }
188                 try {
189                     KeyStore ks = KeyStore.getInstance("JKS");
190                     ks.load(null, null);
191                     CertificateFactory cf = CertificateFactory.getInstance("X.509");
192                     KeyFactory rsaKF = KeyFactory.getInstance("RSA");
193                     KeyFactory dsaKF = KeyFactory.getInstance("DSA");
194 
195                     ByteBuf encodedKeyBuf = PemReader.readPrivateKey(keyFile);
196                     byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
197                     encodedKeyBuf.readBytes(encodedKey).release();
198 
199                     char[] keyPasswordChars = keyPassword.toCharArray();
200                     PKCS8EncodedKeySpec encodedKeySpec = generateKeySpec(keyPasswordChars, encodedKey);
201 
202                     PrivateKey key;
203                     try {
204                         key = rsaKF.generatePrivate(encodedKeySpec);
205                     } catch (InvalidKeySpecException ignore) {
206                         key = dsaKF.generatePrivate(encodedKeySpec);
207                     }
208 
209                     List<Certificate> certChain = new ArrayList<Certificate>();
210                     ByteBuf[] certs = PemReader.readCertificates(certChainFile);
211                     try {
212                         for (ByteBuf buf: certs) {
213                             certChain.add(cf.generateCertificate(new ByteBufInputStream(buf)));
214                         }
215                     } finally {
216                         for (ByteBuf buf: certs) {
217                             buf.release();
218                         }
219                     }
220 
221                     ks.setKeyEntry("key", key, keyPasswordChars, certChain.toArray(new Certificate[certChain.size()]));
222 
223                     if (trustManagerFactory == null) {
224                         // Mimic the way SSLContext.getInstance(KeyManager[], null, null) works
225                         trustManagerFactory = TrustManagerFactory.getInstance(
226                                 TrustManagerFactory.getDefaultAlgorithm());
227                         trustManagerFactory.init((KeyStore) null);
228                     } else {
229                         trustManagerFactory.init(ks);
230                     }
231 
232                     final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
233                     SSLContext.setCertVerifyCallback(ctx, new CertificateVerifier() {
234                         @Override
235                         public boolean verify(long ssl, byte[][] chain, String auth) {
236                             X509Certificate[] peerCerts = certificates(chain);
237                             try {
238                                 manager.checkClientTrusted(peerCerts, auth);
239                                 return true;
240                             } catch (Exception e) {
241                                 logger.debug("verification of certificate failed", e);
242                             }
243                             return false;
244                         }
245                     });
246                 } catch (Exception e) {
247                     throw new SSLException("unable to setup trustmanager", e);
248                 }
249             }
250             sessionContext = new OpenSslServerSessionContext(ctx);
251             success = true;
252         } finally {
253             if (!success) {
254                 destroyPools();
255             }
256         }
257     }
258 
259     @Override
260     public OpenSslServerSessionContext sessionContext() {
261         return sessionContext;
262     }
263 }