1313 * License for the specific language governing permissions and limitations
1414 * under the License.
1515 */
16-
1716package io .netty .handler .ssl ;
1817
19- import io .netty .buffer .ByteBuf ;
20- import io .netty .buffer .ByteBufInputStream ;
21-
22- import javax .net .ssl .KeyManagerFactory ;
23- import javax .net .ssl .SSLContext ;
24- import javax .net .ssl .SSLException ;
25- import javax .net .ssl .SSLSessionContext ;
2618import java .io .File ;
19+ import java .io .IOException ;
20+ import java .security .InvalidAlgorithmParameterException ;
21+ import java .security .InvalidKeyException ;
2722import java .security .KeyFactory ;
2823import java .security .KeyStore ;
24+ import java .security .NoSuchAlgorithmException ;
2925import java .security .PrivateKey ;
3026import java .security .Security ;
3127import java .security .cert .Certificate ;
3632import java .util .Collections ;
3733import java .util .List ;
3834
35+ import javax .crypto .Cipher ;
36+ import javax .crypto .EncryptedPrivateKeyInfo ;
37+ import javax .crypto .NoSuchPaddingException ;
38+ import javax .crypto .SecretKey ;
39+ import javax .crypto .SecretKeyFactory ;
40+ import javax .crypto .spec .PBEKeySpec ;
41+ import javax .net .ssl .KeyManagerFactory ;
42+ import javax .net .ssl .SSLContext ;
43+ import javax .net .ssl .SSLException ;
44+ import javax .net .ssl .SSLSessionContext ;
45+
46+ import io .netty .buffer .ByteBuf ;
47+ import io .netty .buffer .ByteBufInputStream ;
48+
3949/**
4050 * A server-side {@link SslContext} which uses JDK's SSL/TLS implementation.
4151 */
@@ -59,8 +69,7 @@ public JdkSslServerContext(File certChainFile, File keyFile) throws SSLException
5969 *
6070 * @param certChainFile an X.509 certificate chain file in PEM format
6171 * @param keyFile a PKCS#8 private key file in PEM format
62- * @param keyPassword the password of the {@code keyFile}.
63- * {@code null} if it's not password-protected.
72+ * @param keyPassword the password of the {@code keyFile}. {@code null} if it's not password-protected.
6473 */
6574 public JdkSslServerContext (File certChainFile , File keyFile , String keyPassword ) throws SSLException {
6675 this (certChainFile , keyFile , keyPassword , null , null , 0 , 0 );
@@ -71,16 +80,15 @@ public JdkSslServerContext(File certChainFile, File keyFile, String keyPassword)
7180 *
7281 * @param certChainFile an X.509 certificate chain file in PEM format
7382 * @param keyFile a PKCS#8 private key file in PEM format
74- * @param keyPassword the password of the {@code keyFile}.
75- * {@code null} if it's not password-protected.
76- * @param ciphers the cipher suites to enable, in the order of preference.
77- * {@code null} to use the default cipher suites.
78- * @param nextProtocols the application layer protocols to accept, in the order of preference.
79- * {@code null} to disable TLS NPN/ALPN extension.
80- * @param sessionCacheSize the size of the cache used for storing SSL session objects.
81- * {@code 0} to use the default value.
82- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
83- * {@code 0} to use the default value.
83+ * @param keyPassword the password of the {@code keyFile}. {@code null} if it's not password-protected.
84+ * @param ciphers the cipher suites to enable, in the order of preference. {@code null} to use the default cipher
85+ * suites.
86+ * @param nextProtocols the application layer protocols to accept, in the order of preference. {@code null} to
87+ * disable TLS NPN/ALPN extension.
88+ * @param sessionCacheSize the size of the cache used for storing SSL session objects. {@code 0} to use the default
89+ * value.
90+ * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. {@code 0} to use the default
91+ * value.
8492 */
8593 public JdkSslServerContext (
8694 File certChainFile , File keyFile , String keyPassword ,
@@ -106,7 +114,7 @@ public JdkSslServerContext(
106114 }
107115
108116 List <String > list = new ArrayList <String >();
109- for (String p : nextProtocols ) {
117+ for (String p : nextProtocols ) {
110118 if (p == null ) {
111119 break ;
112120 }
@@ -133,7 +141,9 @@ public JdkSslServerContext(
133141 ByteBuf encodedKeyBuf = PemReader .readPrivateKey (keyFile );
134142 byte [] encodedKey = new byte [encodedKeyBuf .readableBytes ()];
135143 encodedKeyBuf .readBytes (encodedKey ).release ();
136- PKCS8EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec (encodedKey );
144+
145+ char [] keyPasswordChars = keyPassword .toCharArray ();
146+ PKCS8EncodedKeySpec encodedKeySpec = generateKeySpec (keyPasswordChars , encodedKey );
137147
138148 PrivateKey key ;
139149 try {
@@ -145,20 +155,20 @@ public JdkSslServerContext(
145155 List <Certificate > certChain = new ArrayList <Certificate >();
146156 ByteBuf [] certs = PemReader .readCertificates (certChainFile );
147157 try {
148- for (ByteBuf buf : certs ) {
158+ for (ByteBuf buf : certs ) {
149159 certChain .add (cf .generateCertificate (new ByteBufInputStream (buf )));
150160 }
151161 } finally {
152- for (ByteBuf buf : certs ) {
162+ for (ByteBuf buf : certs ) {
153163 buf .release ();
154164 }
155165 }
156166
157- ks .setKeyEntry ("key" , key , keyPassword . toCharArray () , certChain .toArray (new Certificate [certChain .size ()]));
167+ ks .setKeyEntry ("key" , key , keyPasswordChars , certChain .toArray (new Certificate [certChain .size ()]));
158168
159169 // Set up key manager factory to use our key store
160170 KeyManagerFactory kmf = KeyManagerFactory .getInstance (algorithm );
161- kmf .init (ks , keyPassword . toCharArray () );
171+ kmf .init (ks , keyPasswordChars );
162172
163173 // Initialize the SSLContext to work with our key managers.
164174 ctx = SSLContext .getInstance (PROTOCOL );
@@ -190,4 +200,36 @@ public List<String> nextProtocols() {
190200 public SSLContext context () {
191201 return ctx ;
192202 }
203+
204+ /**
205+ * Generates a key specification for an (encrypted) private key.
206+ *
207+ * @param password characters, if {@code null} or empty an unencrypted key is assumed
208+ * @param key bytes of the DER encoded private key
209+ * @return a key specification
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 key}
215+ * @throws InvalidAlgorithmParameterException if decryption algorithm parameters are somehow faulty
216+ */
217+ private static PKCS8EncodedKeySpec generateKeySpec (char [] password , byte [] key ) throws IOException ,
218+ NoSuchAlgorithmException , NoSuchPaddingException , InvalidKeySpecException , InvalidKeyException ,
219+ InvalidAlgorithmParameterException {
220+
221+ if (password == null || password .length == 0 ) {
222+ return new PKCS8EncodedKeySpec (key );
223+ }
224+
225+ EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo (key );
226+ SecretKeyFactory keyFactory = SecretKeyFactory .getInstance (encryptedPrivateKeyInfo .getAlgName ());
227+ PBEKeySpec pbeKeySpec = new PBEKeySpec (password );
228+ SecretKey pbeKey = keyFactory .generateSecret (pbeKeySpec );
229+
230+ Cipher cipher = Cipher .getInstance (encryptedPrivateKeyInfo .getAlgName ());
231+ cipher .init (Cipher .DECRYPT_MODE , pbeKey , encryptedPrivateKeyInfo .getAlgParameters ());
232+
233+ return encryptedPrivateKeyInfo .getKeySpec (cipher );
234+ }
193235}
0 commit comments