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  
17  package org.jboss.netty.handler.ssl;
18  
19  import org.jboss.netty.buffer.ChannelBuffer;
20  import org.jboss.netty.buffer.ChannelBufferInputStream;
21  
22  import javax.crypto.Cipher;
23  import javax.crypto.EncryptedPrivateKeyInfo;
24  import javax.crypto.NoSuchPaddingException;
25  import javax.crypto.SecretKey;
26  import javax.crypto.SecretKeyFactory;
27  import javax.crypto.spec.PBEKeySpec;
28  import javax.net.ssl.KeyManagerFactory;
29  import javax.net.ssl.SSLContext;
30  import javax.net.ssl.SSLException;
31  import javax.net.ssl.SSLSessionContext;
32  import java.io.File;
33  import java.io.IOException;
34  import java.security.InvalidAlgorithmParameterException;
35  import java.security.InvalidKeyException;
36  import java.security.KeyFactory;
37  import java.security.KeyStore;
38  import java.security.NoSuchAlgorithmException;
39  import java.security.PrivateKey;
40  import java.security.Security;
41  import java.security.cert.Certificate;
42  import java.security.cert.CertificateFactory;
43  import java.security.spec.InvalidKeySpecException;
44  import java.security.spec.PKCS8EncodedKeySpec;
45  import java.util.ArrayList;
46  import java.util.Collections;
47  import java.util.List;
48  
49  /**
50   * A server-side {@link SslContext} which uses JDK's SSL/TLS implementation.
51   */
52  public final class JdkSslServerContext extends JdkSslContext {
53  
54      private final SSLContext ctx;
55      private final List<String> nextProtocols;
56  
57      /**
58       * Creates a new instance.
59       *
60       * @param certChainFile an X.509 certificate chain file in PEM format
61       * @param keyFile a PKCS#8 private key file in PEM format
62       */
63      public JdkSslServerContext(File certChainFile, File keyFile) throws SSLException {
64          this(certChainFile, keyFile, null);
65      }
66  
67      /**
68       * Creates a new instance.
69       *
70       * @param certChainFile an X.509 certificate chain file in PEM format
71       * @param keyFile a PKCS#8 private key file in PEM format
72       * @param keyPassword the password of the {@code keyFile}.
73       *                    {@code null} if it's not password-protected.
74       */
75      public JdkSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException {
76          this(null, certChainFile, keyFile, keyPassword, null, null, 0, 0);
77      }
78  
79      /**
80       * Creates a new instance.
81       *
82       * @param bufPool the buffer pool which will be used by this context.
83       *                {@code null} to use the default buffer pool.
84       * @param certChainFile an X.509 certificate chain file in PEM format
85       * @param keyFile a PKCS#8 private key file in PEM format
86       * @param keyPassword the password of the {@code keyFile}.
87       *                    {@code null} if it's not password-protected.
88       * @param ciphers the cipher suites to enable, in the order of preference.
89       *                {@code null} to use the default cipher suites.
90       * @param nextProtocols the application layer protocols to accept, in the order of preference.
91       *                      {@code null} to disable TLS NPN/ALPN extension.
92       * @param sessionCacheSize the size of the cache used for storing SSL session objects.
93       *                         {@code 0} to use the default value.
94       * @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
95       *                       {@code 0} to use the default value.
96       */
97      public JdkSslServerContext(
98              SslBufferPool bufPool,
99              File certChainFile, File keyFile, String keyPassword,
100             Iterable<String> ciphers, Iterable<String> nextProtocols,
101             long sessionCacheSize, long sessionTimeout) throws SSLException {
102 
103         super(bufPool, ciphers);
104 
105         if (certChainFile == null) {
106             throw new NullPointerException("certChainFile");
107         }
108         if (keyFile == null) {
109             throw new NullPointerException("keyFile");
110         }
111 
112         if (keyPassword == null) {
113             keyPassword = "";
114         }
115 
116         if (nextProtocols != null && nextProtocols.iterator().hasNext()) {
117             if (!JettyNpnSslEngine.isAvailable()) {
118                 throw new SSLException("NPN/ALPN unsupported: " + nextProtocols);
119             }
120 
121             List<String> list = new ArrayList<String>();
122             for (String p: nextProtocols) {
123                 if (p == null) {
124                     break;
125                 }
126                 list.add(p);
127             }
128 
129             this.nextProtocols = Collections.unmodifiableList(list);
130         } else {
131             this.nextProtocols = Collections.emptyList();
132         }
133 
134         String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
135         if (algorithm == null) {
136             algorithm = "SunX509";
137         }
138 
139         try {
140             KeyStore ks = KeyStore.getInstance("JKS");
141             ks.load(null, null);
142             CertificateFactory cf = CertificateFactory.getInstance("X.509");
143             KeyFactory rsaKF = KeyFactory.getInstance("RSA");
144             KeyFactory dsaKF = KeyFactory.getInstance("DSA");
145 
146             ChannelBuffer encodedKeyBuf = PemReader.readPrivateKey(keyFile);
147             byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
148             encodedKeyBuf.readBytes(encodedKey);
149 
150             char[] keyPasswordChars = keyPassword.toCharArray();
151             PKCS8EncodedKeySpec encodedKeySpec = generateKeySpec(keyPasswordChars, encodedKey);
152 
153             PrivateKey key;
154             try {
155                 key = rsaKF.generatePrivate(encodedKeySpec);
156             } catch (InvalidKeySpecException ignore) {
157                 key = dsaKF.generatePrivate(encodedKeySpec);
158             }
159 
160             List<Certificate> certChain = new ArrayList<Certificate>();
161             for (ChannelBuffer buf: PemReader.readCertificates(certChainFile)) {
162                 certChain.add(cf.generateCertificate(new ChannelBufferInputStream(buf)));
163             }
164 
165             ks.setKeyEntry("key", key, keyPasswordChars, certChain.toArray(new Certificate[certChain.size()]));
166 
167             // Set up key manager factory to use our key store
168             KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
169             kmf.init(ks, keyPasswordChars);
170 
171             // Initialize the SSLContext to work with our key managers.
172             ctx = SSLContext.getInstance(PROTOCOL);
173             ctx.init(kmf.getKeyManagers(), null, null);
174 
175             SSLSessionContext sessCtx = ctx.getServerSessionContext();
176             if (sessionCacheSize > 0) {
177                 sessCtx.setSessionCacheSize((int) Math.min(sessionCacheSize, Integer.MAX_VALUE));
178             }
179             if (sessionTimeout > 0) {
180                 sessCtx.setSessionTimeout((int) Math.min(sessionTimeout, Integer.MAX_VALUE));
181             }
182         } catch (Exception e) {
183             throw new SSLException("failed to initialize the server-side SSL context", e);
184         }
185     }
186 
187     @Override
188     public boolean isClient() {
189         return false;
190     }
191 
192     @Override
193     public List<String> nextProtocols() {
194         return nextProtocols;
195     }
196 
197     @Override
198     public SSLContext context() {
199         return ctx;
200     }
201 
202     /**
203      * Generates a key specification for an (encrypted) private key.
204      *
205      * @param password characters, if {@code null} or empty an unencrypted key is assumed
206      * @param key bytes of the DER encoded private key
207      *
208      * @return a key specification
209      *
210      * @throws IOException if parsing {@code key} fails
211      * @throws NoSuchAlgorithmException if the algorithm used to encrypt {@code key} is unkown
212      * @throws NoSuchPaddingException if the padding scheme specified in the decryption algorithm is unkown
213      * @throws InvalidKeySpecException if the decryption key based on {@code password} cannot be generated
214      * @throws InvalidKeyException if the decryption key based on {@code password} cannot be used to decrypt {@code
215      * key}
216      * @throws InvalidAlgorithmParameterException if decryption algorithm parameters are somehow faulty
217      */
218     private static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key)
219             throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
220                    InvalidKeyException, InvalidAlgorithmParameterException {
221 
222         if (password == null || password.length == 0) {
223             return new PKCS8EncodedKeySpec(key);
224         }
225 
226         EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(key);
227         SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
228         PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
229         SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec);
230 
231         Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
232         cipher.init(Cipher.DECRYPT_MODE, pbeKey, encryptedPrivateKeyInfo.getAlgParameters());
233 
234         return encryptedPrivateKeyInfo.getKeySpec(cipher);
235     }
236 }