@trustin @nmittler @normanmaurer - This is more of a question at this point because I see that the existing JdkSslClientContext uses `null` for the `SSLContext.init(...)` calls for everything but the `TrustStore`.  See [here](https://github.com/netty/netty/blob/master/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java#L123) and [here](https://github.com/netty/netty/blob/master/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java#L151).  Are there any reasons why this has to be `null`, and if I tie in the `KeyManagerFactory` will this do the trick?
 To stay consistent with the server side I think may make sense to get rid of the `TrustManagerFactory` argument in this new constructor.  This will also lend itself better to when we support OpenSSL on the client side.  I'm pulling together a PR to try this out.
 On second thought I am going to wait until we resolve https://github.com/netty/netty/pull/2966.  There will be a good amount of overlap between this and that PR.
 I would like to see mutual authentication support from both server and client.  This is something I have run into a need for recently.  Until Netty supports this, I'm creating my own JDK-based SSLContext.  For the Netty-based SslContext API to be complete, all client and server SslContext implementations will require support for following parameters:  (a) certFile, (b) privateKeyFile, (c) privateKeyPassword, (d) trustChainFile

Explanation of each:
- certFile: public X.509 certificate in PEM format
- privateKeyFile: private key corresponding to public certFile
- privateKeyPassword: password to decrypt private key file (if it's encrypted)
- tustChainFile: PEM format for all accepted certificate authorities
 I am working on a PR for this (both sides...initially just implementing for JDK provider) but I am going to wait to submit it because I have another PR #2966 in the same area that would be messy for merging.
 I actually did not consider TLS mutual authentication so far.  Please feel free to come up with a pull request.
 @trustin After  #2966 is resolved I will submit one.
 PR https://github.com/netty/netty/issues/3041 was submitted to address this issue.
 Cherry-picked

ssl mutual authentication (JDK provider)
I have a usecase that requires a client to do mutual authentication.  I have seen a few stackoverflow posts related to achieving mutual auth by setting up the `SSLContext` manually, and then invoking `SSLEngine.setNeedClientAuth(true)` before putting this SSLEngine into the pipeline.  I am using `SslContext.newClientContext` and this seems like a convenient abstraction provided by netty (I would like to continue to use this if possible).  Would extending these `SslContext` constructors to take an additional argument of `KeyManagerFactory` be sufficient (and desired) to allowing the Netty abstraction to support building an `SSLContext` object that supports mutual auth?

handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java handler/src/main/java/io/netty/handler/ssl/SslContext.java
handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java handler/src/main/java/io/netty/handler/ssl/SslContext.java
handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java handler/src/test/java/io/netty/handler/ssl/JdkSslServerContextTest.java handler/src/test/resources/io/netty/handler/ssl/test2.crt handler/src/test/resources/io/netty/handler/ssl/test2_encrypted.pem handler/src/test/resources/io/netty/handler/ssl/test2_unencrypted.pem
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java
index 7d4e7166c86..479bb24e570 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java
@@ -16,20 +16,16 @@
 
 package io.netty.handler.ssl;
 
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufInputStream;
 
 import java.io.File;
-import java.security.KeyStore;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
 
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLSessionContext;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.TrustManagerFactory;
-import javax.security.auth.x500.X500Principal;
 
 /**
  * A client-side {@link SslContext} which uses JDK's SSL/TLS implementation.
@@ -126,46 +122,93 @@ public JdkSslClientContext(
             File certChainFile, TrustManagerFactory trustManagerFactory,
             Iterable<String> ciphers, CipherSuiteFilter cipherFilter, JdkApplicationProtocolNegotiator apn,
             long sessionCacheSize, long sessionTimeout) throws SSLException {
+        this(certChainFile, trustManagerFactory, null, null, null, null,
+                ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
+    }
+
+    /**
+     * Creates a new instance.
+     * @param trustCertChainFile an X.509 certificate chain file in PEM format.
+     *                      {@code null} to use the system default
+     * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
+     *                            that verifies the certificates sent from servers.
+     *                            {@code null} to use the default or the results of parsing {@code trustCertChainFile}
+     * @param keyCertChainFile an X.509 certificate chain file in PEM format.
+     *                      This provides the public key for mutual authentication.
+     *                      {@code null} to use the system default
+     * @param keyFile a PKCS#8 private key file in PEM format.
+     *                      This provides the private key for mutual authentication.
+     *                      {@code null} for no mutual authentication.
+     * @param keyPassword the password of the {@code keyFile}.
+     *                    {@code null} if it's not password-protected.
+     *                    Ignored if {@code keyFile} is {@code null}.
+     * @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s
+     *                          that is used to encrypt data being sent to servers.
+     *                          {@code null} to use the default or the results of parsing
+     *                          {@code keyCertChainFile} and {@code keyFile}.
+     * @param ciphers the cipher suites to enable, in the order of preference.
+     *                {@code null} to use the default cipher suites.
+     * @param cipherFilter a filter to apply over the supplied list of ciphers
+     * @param apn Provides a means to configure parameters related to application protocol negotiation.
+     * @param sessionCacheSize the size of the cache used for storing SSL session objects.
+     *                         {@code 0} to use the default value.
+     * @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
+     *                       {@code 0} to use the default value.
+     */
+    public JdkSslClientContext(File trustCertChainFile, TrustManagerFactory trustManagerFactory,
+            File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory,
+            Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
+            long sessionCacheSize, long sessionTimeout) throws SSLException {
+        this(trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword, keyManagerFactory,
+                ciphers, cipherFilter, toNegotiator(apn, false), sessionCacheSize, sessionTimeout);
+    }
 
+    /**
+     * Creates a new instance.
+     * @param trustCertChainFile an X.509 certificate chain file in PEM format.
+     *                      {@code null} to use the system default
+     * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
+     *                            that verifies the certificates sent from servers.
+     *                            {@code null} to use the default or the results of parsing {@code trustCertChainFile}
+     * @param keyCertChainFile an X.509 certificate chain file in PEM format.
+     *                      This provides the public key for mutual authentication.
+     *                      {@code null} to use the system default
+     * @param keyFile a PKCS#8 private key file in PEM format.
+     *                      This provides the private key for mutual authentication.
+     *                      {@code null} for no mutual authentication.
+     * @param keyPassword the password of the {@code keyFile}.
+     *                    {@code null} if it's not password-protected.
+     *                    Ignored if {@code keyFile} is {@code null}.
+     * @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s
+     *                          that is used to encrypt data being sent to servers.
+     *                          {@code null} to use the default or the results of parsing
+     *                          {@code keyCertChainFile} and {@code keyFile}.
+     * @param ciphers the cipher suites to enable, in the order of preference.
+     *                {@code null} to use the default cipher suites.
+     * @param cipherFilter a filter to apply over the supplied list of ciphers
+     * @param apn Application Protocol Negotiator object.
+     * @param sessionCacheSize the size of the cache used for storing SSL session objects.
+     *                         {@code 0} to use the default value.
+     * @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
+     *                       {@code 0} to use the default value.
+     */
+    public JdkSslClientContext(File trustCertChainFile, TrustManagerFactory trustManagerFactory,
+            File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory,
+            Iterable<String> ciphers, CipherSuiteFilter cipherFilter, JdkApplicationProtocolNegotiator apn,
+            long sessionCacheSize, long sessionTimeout) throws SSLException {
         super(ciphers, cipherFilter, apn);
 
         try {
-            if (certChainFile == null) {
-                ctx = SSLContext.getInstance(PROTOCOL);
-                if (trustManagerFactory == null) {
-                    ctx.init(null, null, null);
-                } else {
-                    trustManagerFactory.init((KeyStore) null);
-                    ctx.init(null, trustManagerFactory.getTrustManagers(), null);
-                }
-            } else {
-                KeyStore ks = KeyStore.getInstance("JKS");
-                ks.load(null, null);
-                CertificateFactory cf = CertificateFactory.getInstance("X.509");
-
-                ByteBuf[] certs = PemReader.readCertificates(certChainFile);
-                try {
-                    for (ByteBuf buf: certs) {
-                        X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteBufInputStream(buf));
-                        X500Principal principal = cert.getSubjectX500Principal();
-                        ks.setCertificateEntry(principal.getName("RFC2253"), cert);
-                    }
-                } finally {
-                    for (ByteBuf buf: certs) {
-                        buf.release();
-                    }
-                }
-
-                // Set up trust manager factory to use our key store.
-                if (trustManagerFactory == null) {
-                    trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
-                }
-                trustManagerFactory.init(ks);
-
-                // Initialize the SSLContext to work with the trust managers.
-                ctx = SSLContext.getInstance(PROTOCOL);
-                ctx.init(null, trustManagerFactory.getTrustManagers(), null);
+            if (trustCertChainFile != null) {
+                trustManagerFactory = buildTrustManagerFactory(trustCertChainFile, trustManagerFactory);
+            }
+            if (keyFile != null) {
+                keyManagerFactory = buildKeyManagerFactory(keyCertChainFile, keyFile, keyPassword, keyManagerFactory);
             }
+            ctx = SSLContext.getInstance(PROTOCOL);
+            ctx.init(keyManagerFactory == null ? null : keyManagerFactory.getKeyManagers(),
+                     trustManagerFactory == null ? null : trustManagerFactory.getTrustManagers(),
+                     null);
 
             SSLSessionContext sessCtx = ctx.getClientSessionContext();
             if (sessionCacheSize > 0) {
@@ -175,7 +218,7 @@ public JdkSslClientContext(
                 sessCtx.setSessionTimeout((int) Math.min(sessionTimeout, Integer.MAX_VALUE));
             }
         } catch (Exception e) {
-            throw new SSLException("failed to initialize the server-side SSL context", e);
+            throw new SSLException("failed to initialize the client-side SSL context", e);
         }
     }
 
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
index e998bc06b23..08ae41b1124 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
@@ -17,10 +17,30 @@
 package io.netty.handler.ssl;
 
 import static io.netty.util.internal.ObjectUtil.checkNotNull;
+import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufInputStream;
 import io.netty.util.internal.logging.InternalLogger;
 import io.netty.util.internal.logging.InternalLoggerFactory;
 
+import java.io.File;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyException;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -28,9 +48,18 @@
 import java.util.List;
 import java.util.Set;
 
+import javax.crypto.Cipher;
+import javax.crypto.EncryptedPrivateKeyInfo;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.TrustManagerFactory;
+import javax.security.auth.x500.X500Principal;
 
 /**
  * An {@link SslContext} which uses JDK's SSL/TLS implementation.
@@ -257,4 +286,156 @@ static JdkApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig c
             .append(config.protocol()).append(" protocol").toString());
         }
     }
+
+    /**
+     * Build a {@link KeyManagerFactory} based upon a key file, key file password, and a certificate chain.
+     * @param certChainFile a X.509 certificate chain file in PEM format
+     * @param keyFile a PKCS#8 private key file in PEM format
+     * @param keyPassword the password of the {@code keyFile}.
+     *                    {@code null} if it's not password-protected.
+     * @param kmf The existing {@link KeyManagerFactory} that will be used if not {@code null}
+     * @return A {@link KeyManagerFactory} based upon a key file, key file password, and a certificate chain.
+     */
+    protected static KeyManagerFactory buildKeyManagerFactory(File certChainFile, File keyFile, String keyPassword,
+            KeyManagerFactory kmf)
+                    throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException,
+                    NoSuchPaddingException, InvalidKeySpecException, InvalidAlgorithmParameterException,
+                    CertificateException, KeyException, IOException {
+        String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
+        if (algorithm == null) {
+            algorithm = "SunX509";
+        }
+        return buildKeyManagerFactory(certChainFile, algorithm, keyFile, keyPassword, kmf);
+    }
+
+    /**
+     * Build a {@link KeyManagerFactory} based upon a key algorithm, key file, key file password,
+     * and a certificate chain.
+     * @param certChainFile a X.509 certificate chain file in PEM format
+     * @param keyAlgorithm the standard name of the requested algorithm. See the Java Secure Socket Extension
+     * Reference Guide for information about standard algorithm names.
+     * @param keyFile a PKCS#8 private key file in PEM format
+     * @param keyPassword the password of the {@code keyFile}.
+     *                    {@code null} if it's not password-protected.
+     * @param kmf The existing {@link KeyManagerFactory} that will be used if not {@code null}
+     * @return A {@link KeyManagerFactory} based upon a key algorithm, key file, key file password,
+     * and a certificate chain.
+     */
+    protected static KeyManagerFactory buildKeyManagerFactory(File certChainFile,
+            String keyAlgorithm, File keyFile, String keyPassword, KeyManagerFactory kmf)
+                    throws KeyStoreException, NoSuchAlgorithmException, NoSuchPaddingException,
+                    InvalidKeySpecException, InvalidAlgorithmParameterException, IOException,
+                    CertificateException, KeyException, UnrecoverableKeyException {
+        KeyStore ks = KeyStore.getInstance("JKS");
+        ks.load(null, null);
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        KeyFactory rsaKF = KeyFactory.getInstance("RSA");
+        KeyFactory dsaKF = KeyFactory.getInstance("DSA");
+
+        ByteBuf encodedKeyBuf = PemReader.readPrivateKey(keyFile);
+        byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
+        encodedKeyBuf.readBytes(encodedKey).release();
+
+        char[] keyPasswordChars = keyPassword == null ? new char[0] : keyPassword.toCharArray();
+        PKCS8EncodedKeySpec encodedKeySpec = generateKeySpec(keyPasswordChars, encodedKey);
+
+        PrivateKey key;
+        try {
+            key = rsaKF.generatePrivate(encodedKeySpec);
+        } catch (InvalidKeySpecException ignore) {
+            key = dsaKF.generatePrivate(encodedKeySpec);
+        }
+
+        List<Certificate> certChain = new ArrayList<Certificate>();
+        ByteBuf[] certs = PemReader.readCertificates(certChainFile);
+        try {
+            for (ByteBuf buf: certs) {
+                certChain.add(cf.generateCertificate(new ByteBufInputStream(buf)));
+            }
+        } finally {
+            for (ByteBuf buf: certs) {
+                buf.release();
+            }
+        }
+
+        ks.setKeyEntry("key", key, keyPasswordChars, certChain.toArray(new Certificate[certChain.size()]));
+
+        // Set up key manager factory to use our key store
+        if (kmf == null) {
+            kmf = KeyManagerFactory.getInstance(keyAlgorithm);
+        }
+        kmf.init(ks, keyPasswordChars);
+
+        return kmf;
+    }
+
+    /**
+     * Build a {@link TrustManagerFactory} from a certificate chain file.
+     * @param certChainFile The certificate file to build from.
+     * @param trustManagerFactory The existing {@link TrustManagerFactory} that will be used if not {@code null}.
+     * @return A {@link TrustManagerFactory} which contains the certificates in {@code certChainFile}
+     */
+    protected static TrustManagerFactory buildTrustManagerFactory(File certChainFile,
+            TrustManagerFactory trustManagerFactory)
+                    throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
+        KeyStore ks = KeyStore.getInstance("JKS");
+        ks.load(null, null);
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+        ByteBuf[] certs = PemReader.readCertificates(certChainFile);
+        try {
+            for (ByteBuf buf: certs) {
+                X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteBufInputStream(buf));
+                X500Principal principal = cert.getSubjectX500Principal();
+                ks.setCertificateEntry(principal.getName("RFC2253"), cert);
+            }
+        } finally {
+            for (ByteBuf buf: certs) {
+                buf.release();
+            }
+        }
+
+        // Set up trust manager factory to use our key store.
+        if (trustManagerFactory == null) {
+            trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+        }
+        trustManagerFactory.init(ks);
+
+        return trustManagerFactory;
+    }
+
+    /**
+     * Generates a key specification for an (encrypted) private key.
+     *
+     * @param password characters, if {@code null} or empty an unencrypted key is assumed
+     * @param key bytes of the DER encoded private key
+     *
+     * @return a key specification
+     *
+     * @throws IOException if parsing {@code key} fails
+     * @throws NoSuchAlgorithmException if the algorithm used to encrypt {@code key} is unkown
+     * @throws NoSuchPaddingException if the padding scheme specified in the decryption algorithm is unkown
+     * @throws InvalidKeySpecException if the decryption key based on {@code password} cannot be generated
+     * @throws InvalidKeyException if the decryption key based on {@code password} cannot be used to decrypt
+     *                             {@code key}
+     * @throws InvalidAlgorithmParameterException if decryption algorithm parameters are somehow faulty
+     */
+    private static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key)
+            throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
+                   InvalidKeyException, InvalidAlgorithmParameterException {
+
+        if (password == null || password.length == 0) {
+            return new PKCS8EncodedKeySpec(key);
+        }
+
+        EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(key);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
+        PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
+        SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec);
+
+        Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
+        cipher.init(Cipher.DECRYPT_MODE, pbeKey, encryptedPrivateKeyInfo.getAlgParameters());
+
+        return encryptedPrivateKeyInfo.getKeySpec(cipher);
+    }
 }
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java
index 9a22a7c1ce1..118f8f37f0c 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslServerContext.java
@@ -16,35 +16,15 @@
 
 package io.netty.handler.ssl;
 
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufInputStream;
-
 import java.io.File;
-import java.io.IOException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyFactory;
-import java.security.KeyStore;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.Security;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.crypto.Cipher;
-import javax.crypto.EncryptedPrivateKeyInfo;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
+
+import javax.net.ssl.KeyManager;
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
 
 /**
  * A server-side {@link SslContext} which uses JDK's SSL/TLS implementation.
@@ -120,67 +100,92 @@ public JdkSslServerContext(
             File certChainFile, File keyFile, String keyPassword,
             Iterable<String> ciphers, CipherSuiteFilter cipherFilter, JdkApplicationProtocolNegotiator apn,
             long sessionCacheSize, long sessionTimeout) throws SSLException {
+        this(null, null, certChainFile, keyFile, keyPassword, null,
+                ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
+    }
 
-        super(ciphers, cipherFilter, apn);
-
-        if (certChainFile == null) {
-            throw new NullPointerException("certChainFile");
-        }
-        if (keyFile == null) {
-            throw new NullPointerException("keyFile");
-        }
-
-        if (keyPassword == null) {
-            keyPassword = "";
-        }
+    /**
+     * Creates a new instance.
+     * @param trustCertChainFile an X.509 certificate chain file in PEM format.
+     *                      This provides the certificate chains used for mutual authentication.
+     *                      {@code null} to use the system default
+     * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
+     *                            that verifies the certificates sent from clients.
+     *                            {@code null} to use the default or the results of parsing {@code trustCertChainFile}.
+     * @param keyCertChainFile an X.509 certificate chain file in PEM format
+     * @param keyFile a PKCS#8 private key file in PEM format
+     * @param keyPassword the password of the {@code keyFile}.
+     *                    {@code null} if it's not password-protected.
+     * @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s
+     *                          that is used to encrypt data being sent to clients.
+     *                          {@code null} to use the default or the results of parsing
+     *                          {@code keyCertChainFile} and {@code keyFile}.
+     * @param ciphers the cipher suites to enable, in the order of preference.
+     *                {@code null} to use the default cipher suites.
+     * @param cipherFilter a filter to apply over the supplied list of ciphers
+     *                Only required if {@code provider} is {@link SslProvider#JDK}
+     * @param apn Provides a means to configure parameters related to application protocol negotiation.
+     * @param sessionCacheSize the size of the cache used for storing SSL session objects.
+     *                         {@code 0} to use the default value.
+     * @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
+     *                       {@code 0} to use the default value.
+     */
+    public JdkSslServerContext(File trustCertChainFile, TrustManagerFactory trustManagerFactory,
+            File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory,
+            Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
+            long sessionCacheSize, long sessionTimeout) throws SSLException {
+        this(trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword, keyManagerFactory,
+                ciphers, cipherFilter, toNegotiator(apn, true), sessionCacheSize, sessionTimeout);
+    }
 
-        String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
-        if (algorithm == null) {
-            algorithm = "SunX509";
+    /**
+     * Creates a new instance.
+     * @param trustCertChainFile an X.509 certificate chain file in PEM format.
+     *                      This provides the certificate chains used for mutual authentication.
+     *                      {@code null} to use the system default
+     * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
+     *                            that verifies the certificates sent from clients.
+     *                            {@code null} to use the default or the results of parsing {@code trustCertChainFile}
+     * @param keyCertChainFile an X.509 certificate chain file in PEM format
+     * @param keyFile a PKCS#8 private key file in PEM format
+     * @param keyPassword the password of the {@code keyFile}.
+     *                    {@code null} if it's not password-protected.
+     * @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s
+     *                          that is used to encrypt data being sent to clients.
+     *                          {@code null} to use the default or the results of parsing
+     *                          {@code keyCertChainFile} and {@code keyFile}.
+     * @param ciphers the cipher suites to enable, in the order of preference.
+     *                {@code null} to use the default cipher suites.
+     * @param cipherFilter a filter to apply over the supplied list of ciphers
+     *                Only required if {@code provider} is {@link SslProvider#JDK}
+     * @param apn Application Protocol Negotiator object.
+     * @param sessionCacheSize the size of the cache used for storing SSL session objects.
+     *                         {@code 0} to use the default value.
+     * @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
+     *                       {@code 0} to use the default value.
+     */
+    public JdkSslServerContext(File trustCertChainFile, TrustManagerFactory trustManagerFactory,
+            File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory,
+            Iterable<String> ciphers, CipherSuiteFilter cipherFilter, JdkApplicationProtocolNegotiator apn,
+            long sessionCacheSize, long sessionTimeout) throws SSLException {
+        super(ciphers, cipherFilter, apn);
+        if (keyFile == null && keyManagerFactory == null) {
+            throw new NullPointerException("keyFile, keyManagerFactory");
         }
 
         try {
-            KeyStore ks = KeyStore.getInstance("JKS");
-            ks.load(null, null);
-            CertificateFactory cf = CertificateFactory.getInstance("X.509");
-            KeyFactory rsaKF = KeyFactory.getInstance("RSA");
-            KeyFactory dsaKF = KeyFactory.getInstance("DSA");
-
-            ByteBuf encodedKeyBuf = PemReader.readPrivateKey(keyFile);
-            byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
-            encodedKeyBuf.readBytes(encodedKey).release();
-
-            char[] keyPasswordChars = keyPassword.toCharArray();
-            PKCS8EncodedKeySpec encodedKeySpec = generateKeySpec(keyPasswordChars, encodedKey);
-
-            PrivateKey key;
-            try {
-                key = rsaKF.generatePrivate(encodedKeySpec);
-            } catch (InvalidKeySpecException ignore) {
-                key = dsaKF.generatePrivate(encodedKeySpec);
+            if (trustCertChainFile != null) {
+                trustManagerFactory = buildTrustManagerFactory(trustCertChainFile, trustManagerFactory);
             }
-
-            List<Certificate> certChain = new ArrayList<Certificate>();
-            ByteBuf[] certs = PemReader.readCertificates(certChainFile);
-            try {
-                for (ByteBuf buf: certs) {
-                    certChain.add(cf.generateCertificate(new ByteBufInputStream(buf)));
-                }
-            } finally {
-                for (ByteBuf buf: certs) {
-                    buf.release();
-                }
+            if (keyFile != null) {
+                keyManagerFactory = buildKeyManagerFactory(keyCertChainFile, keyFile, keyPassword, keyManagerFactory);
             }
 
-            ks.setKeyEntry("key", key, keyPasswordChars, certChain.toArray(new Certificate[certChain.size()]));
-
-            // Set up key manager factory to use our key store
-            KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
-            kmf.init(ks, keyPasswordChars);
-
             // Initialize the SSLContext to work with our key managers.
             ctx = SSLContext.getInstance(PROTOCOL);
-            ctx.init(kmf.getKeyManagers(), null, null);
+            ctx.init(keyManagerFactory.getKeyManagers(),
+                     trustManagerFactory == null ? null : trustManagerFactory.getTrustManagers(),
+                     null);
 
             SSLSessionContext sessCtx = ctx.getServerSessionContext();
             if (sessionCacheSize > 0) {
@@ -203,39 +208,4 @@ public boolean isClient() {
     public SSLContext context() {
         return ctx;
     }
-
-    /**
-     * Generates a key specification for an (encrypted) private key.
-     *
-     * @param password characters, if {@code null} or empty an unencrypted key is assumed
-     * @param key bytes of the DER encoded private key
-     *
-     * @return a key specification
-     *
-     * @throws IOException if parsing {@code key} fails
-     * @throws NoSuchAlgorithmException if the algorithm used to encrypt {@code key} is unkown
-     * @throws NoSuchPaddingException if the padding scheme specified in the decryption algorithm is unkown
-     * @throws InvalidKeySpecException if the decryption key based on {@code password} cannot be generated
-     * @throws InvalidKeyException if the decryption key based on {@code password} cannot be used to decrypt
-     *                             {@code key}
-     * @throws InvalidAlgorithmParameterException if decryption algorithm parameters are somehow faulty
-     */
-    private static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key)
-            throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
-                   InvalidKeyException, InvalidAlgorithmParameterException {
-
-        if (password == null || password.length == 0) {
-            return new PKCS8EncodedKeySpec(key);
-        }
-
-        EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(key);
-        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
-        PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
-        SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec);
-
-        Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
-        cipher.init(Cipher.DECRYPT_MODE, pbeKey, encryptedPrivateKeyInfo.getAlgParameters());
-
-        return encryptedPrivateKeyInfo.getKeySpec(cipher);
-    }
 }
diff --git a/handler/src/main/java/io/netty/handler/ssl/SslContext.java b/handler/src/main/java/io/netty/handler/ssl/SslContext.java
index 494c4f25d72..38abd598e77 100644
--- a/handler/src/main/java/io/netty/handler/ssl/SslContext.java
+++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java
@@ -23,6 +23,8 @@
 import java.io.File;
 import java.util.List;
 
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLException;
@@ -176,11 +178,50 @@ public static SslContext newServerContext(
      *                       {@code 0} to use the default value.
      * @return a new server-side {@link SslContext}
      */
-    public static SslContext newServerContext(
-            SslProvider provider,
+    public static SslContext newServerContext(SslProvider provider,
             File certChainFile, File keyFile, String keyPassword,
             Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
             long sessionCacheSize, long sessionTimeout) throws SSLException {
+        return newServerContext(provider, null, null, certChainFile, keyFile, keyPassword, null,
+                ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
+    }
+
+    /**
+     * Creates a new server-side {@link SslContext}.
+     * @param provider the {@link SslContext} implementation to use.
+     *                 {@code null} to use the current default one.
+     * @param trustCertChainFile an X.509 certificate chain file in PEM format.
+     *                      This provides the certificate chains used for mutual authentication.
+     *                      {@code null} to use the system default
+     * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
+     *                            that verifies the certificates sent from clients.
+     *                            {@code null} to use the default or the results of parsing {@code trustCertChainFile}.
+     *                            This parameter is ignored if {@code provider} is not {@link SslProvider#JDK}.
+     * @param keyCertChainFile an X.509 certificate chain file in PEM format
+     * @param keyFile a PKCS#8 private key file in PEM format
+     * @param keyPassword the password of the {@code keyFile}.
+     *                    {@code null} if it's not password-protected.
+     * @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s
+     *                          that is used to encrypt data being sent to clients.
+     *                          {@code null} to use the default or the results of parsing
+     *                          {@code keyCertChainFile} and {@code keyFile}.
+     *                          This parameter is ignored if {@code provider} is not {@link SslProvider#JDK}.
+     * @param ciphers the cipher suites to enable, in the order of preference.
+     *                {@code null} to use the default cipher suites.
+     * @param cipherFilter a filter to apply over the supplied list of ciphers
+     *                Only required if {@code provider} is {@link SslProvider#JDK}
+     * @param apn Provides a means to configure parameters related to application protocol negotiation.
+     * @param sessionCacheSize the size of the cache used for storing SSL session objects.
+     *                         {@code 0} to use the default value.
+     * @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
+     *                       {@code 0} to use the default value.
+     * @return a new server-side {@link SslContext}
+     */
+    public static SslContext newServerContext(SslProvider provider,
+            File trustCertChainFile, TrustManagerFactory trustManagerFactory,
+            File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory,
+            Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
+            long sessionCacheSize, long sessionTimeout) throws SSLException {
 
         if (provider == null) {
             provider = OpenSsl.isAvailable()? SslProvider.OPENSSL : SslProvider.JDK;
@@ -189,11 +230,14 @@ public static SslContext newServerContext(
         switch (provider) {
             case JDK:
                 return new JdkSslServerContext(
-                        certChainFile, keyFile, keyPassword,
-                        ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
+                        trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword,
+                        keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
             case OPENSSL:
+                if (trustCertChainFile != null) {
+                    throw new UnsupportedOperationException("OpenSSL provider does not support mutual authentication");
+                }
                 return new OpenSslServerContext(
-                        certChainFile, keyFile, keyPassword,
+                        keyCertChainFile, keyFile, keyPassword,
                         ciphers, apn, sessionCacheSize, sessionTimeout);
             default:
                 throw new Error(provider.toString());
@@ -359,19 +403,61 @@ public static SslContext newClientContext(
      *
      * @return a new client-side {@link SslContext}
      */
-    public static SslContext newClientContext(
-            SslProvider provider,
+    public static SslContext newClientContext(SslProvider provider,
             File certChainFile, TrustManagerFactory trustManagerFactory,
             Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
             long sessionCacheSize, long sessionTimeout) throws SSLException {
+        return newClientContext(provider, certChainFile, trustManagerFactory, null, null, null, null,
+                ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
+    }
+
+    /**
+     * Creates a new client-side {@link SslContext}.
+     * @param provider the {@link SslContext} implementation to use.
+     *                 {@code null} to use the current default one.
+     * @param trustCertChainFile an X.509 certificate chain file in PEM format.
+     *                      {@code null} to use the system default
+     * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
+     *                            that verifies the certificates sent from servers.
+     *                            {@code null} to use the default or the results of parsing {@code trustCertChainFile}.
+     *                            This parameter is ignored if {@code provider} is not {@link SslProvider#JDK}.
+     * @param keyCertChainFile an X.509 certificate chain file in PEM format.
+     *                      This provides the public key for mutual authentication.
+     *                      {@code null} to use the system default
+     * @param keyFile a PKCS#8 private key file in PEM format.
+     *                      This provides the private key for mutual authentication.
+     *                      {@code null} for no mutual authentication.
+     * @param keyPassword the password of the {@code keyFile}.
+     *                    {@code null} if it's not password-protected.
+     *                    Ignored if {@code keyFile} is {@code null}.
+     * @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s
+     *                          that is used to encrypt data being sent to servers.
+     *                          {@code null} to use the default or the results of parsing
+     *                          {@code keyCertChainFile} and {@code keyFile}.
+     *                          This parameter is ignored if {@code provider} is not {@link SslProvider#JDK}.
+     * @param ciphers the cipher suites to enable, in the order of preference.
+     *                {@code null} to use the default cipher suites.
+     * @param cipherFilter a filter to apply over the supplied list of ciphers
+     * @param apn Provides a means to configure parameters related to application protocol negotiation.
+     * @param sessionCacheSize the size of the cache used for storing SSL session objects.
+     *                         {@code 0} to use the default value.
+     * @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
+     *                       {@code 0} to use the default value.
+     *
+     * @return a new client-side {@link SslContext}
+     */
+    public static SslContext newClientContext(SslProvider provider,
+            File trustCertChainFile, TrustManagerFactory trustManagerFactory,
+            File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory,
+            Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
+            long sessionCacheSize, long sessionTimeout) throws SSLException {
 
         if (provider != null && provider != SslProvider.JDK) {
             throw new SSLException("client context unsupported for: " + provider);
         }
 
-        return new JdkSslClientContext(
-                certChainFile, trustManagerFactory,
-                ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
+        return new JdkSslClientContext(trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword,
+                keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
     }
 
     SslContext() { }

diff --git a/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java b/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java
index 3b3f60b59a7..41b2da74364 100644
--- a/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java
+++ b/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java
@@ -41,6 +41,7 @@
 import io.netty.util.NetUtil;
 import io.netty.util.concurrent.Future;
 
+import java.io.File;
 import java.net.InetSocketAddress;
 import java.security.cert.CertificateException;
 import java.util.List;
@@ -315,6 +316,57 @@ public String select(List<String> protocols) {
         }
     }
 
+    @Test
+    public void testMutualAuthSameCerts() throws Exception {
+        mySetupMutualAuth(new File(getClass().getResource("test_unencrypted.pem").getFile()),
+                          new File(getClass().getResource("test.crt").getFile()),
+                          null);
+        runTest(null);
+    }
+
+    @Test
+    public void testMutualAuthDiffCerts() throws Exception {
+        File serverKeyFile = new File(getClass().getResource("test_encrypted.pem").getFile());
+        File serverCrtFile = new File(getClass().getResource("test.crt").getFile());
+        String serverKeyPassword = "12345";
+        File clientKeyFile = new File(getClass().getResource("test2_encrypted.pem").getFile());
+        File clientCrtFile = new File(getClass().getResource("test2.crt").getFile());
+        String clientKeyPassword = "12345";
+        mySetupMutualAuth(clientCrtFile, serverKeyFile, serverCrtFile, serverKeyPassword,
+                serverCrtFile, clientKeyFile, clientCrtFile, clientKeyPassword);
+        runTest(null);
+    }
+
+    @Test
+    public void testMutualAuthDiffCertsServerFailure() throws Exception {
+        File serverKeyFile = new File(getClass().getResource("test_encrypted.pem").getFile());
+        File serverCrtFile = new File(getClass().getResource("test.crt").getFile());
+        String serverKeyPassword = "12345";
+        File clientKeyFile = new File(getClass().getResource("test2_encrypted.pem").getFile());
+        File clientCrtFile = new File(getClass().getResource("test2.crt").getFile());
+        String clientKeyPassword = "12345";
+        // Client trusts server but server only trusts itself
+        mySetupMutualAuth(serverCrtFile, serverKeyFile, serverCrtFile, serverKeyPassword,
+                serverCrtFile, clientKeyFile, clientCrtFile, clientKeyPassword);
+        assertTrue(serverLatch.await(2, TimeUnit.SECONDS));
+        assertTrue(serverException instanceof SSLHandshakeException);
+    }
+
+    @Test
+    public void testMutualAuthDiffCertsClientFailure() throws Exception {
+        File serverKeyFile = new File(getClass().getResource("test_unencrypted.pem").getFile());
+        File serverCrtFile = new File(getClass().getResource("test.crt").getFile());
+        String serverKeyPassword = null;
+        File clientKeyFile = new File(getClass().getResource("test2_unencrypted.pem").getFile());
+        File clientCrtFile = new File(getClass().getResource("test2.crt").getFile());
+        String clientKeyPassword = null;
+        // Server trusts client but client only trusts itself
+        mySetupMutualAuth(clientCrtFile, serverKeyFile, serverCrtFile, serverKeyPassword,
+                clientCrtFile, clientKeyFile, clientCrtFile, clientKeyPassword);
+        assertTrue(clientLatch.await(2, TimeUnit.SECONDS));
+        assertTrue(clientException instanceof SSLHandshakeException);
+    }
+
     private void mySetup(JdkApplicationProtocolNegotiator apn) throws InterruptedException, SSLException,
             CertificateException {
         mySetup(apn, apn);
@@ -385,6 +437,82 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E
         clientChannel = ccf.channel();
     }
 
+    private void mySetupMutualAuth(File keyFile, File crtFile, String keyPassword)
+            throws SSLException, CertificateException, InterruptedException {
+        mySetupMutualAuth(crtFile, keyFile, crtFile, keyPassword, crtFile, keyFile, crtFile, keyPassword);
+    }
+
+    private void mySetupMutualAuth(
+            File servertTrustCrtFile, File serverKeyFile, File serverCrtFile, String serverKeyPassword,
+            File clientTrustCrtFile, File clientKeyFile, File clientCrtFile, String clientKeyPassword)
+            throws InterruptedException, SSLException, CertificateException {
+        serverSslCtx = new JdkSslServerContext(servertTrustCrtFile, null,
+                serverCrtFile, serverKeyFile, serverKeyPassword, null,
+                null, IdentityCipherSuiteFilter.INSTANCE, (ApplicationProtocolConfig) null, 0, 0);
+        clientSslCtx = new JdkSslClientContext(clientTrustCrtFile, null,
+                clientCrtFile, clientKeyFile, clientKeyPassword, null,
+                null, IdentityCipherSuiteFilter.INSTANCE, (ApplicationProtocolConfig) null, 0, 0);
+
+        serverConnectedChannel = null;
+        sb = new ServerBootstrap();
+        cb = new Bootstrap();
+
+        sb.group(new NioEventLoopGroup(), new NioEventLoopGroup());
+        sb.channel(NioServerSocketChannel.class);
+        sb.childHandler(new ChannelInitializer<Channel>() {
+            @Override
+            protected void initChannel(Channel ch) throws Exception {
+                ChannelPipeline p = ch.pipeline();
+                SSLEngine engine = serverSslCtx.newEngine(ch.alloc());
+                engine.setUseClientMode(false);
+                engine.setNeedClientAuth(true);
+                p.addLast(new SslHandler(engine));
+                p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch));
+                p.addLast(new ChannelHandlerAdapter() {
+                    @Override
+                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+                        if (cause.getCause() instanceof SSLHandshakeException) {
+                            serverException = cause.getCause();
+                            serverLatch.countDown();
+                        } else {
+                            ctx.fireExceptionCaught(cause);
+                        }
+                    }
+                });
+                serverConnectedChannel = ch;
+            }
+        });
+
+        cb.group(new NioEventLoopGroup());
+        cb.channel(NioSocketChannel.class);
+        cb.handler(new ChannelInitializer<Channel>() {
+            @Override
+            protected void initChannel(Channel ch) throws Exception {
+                ChannelPipeline p = ch.pipeline();
+                p.addLast(clientSslCtx.newHandler(ch.alloc()));
+                p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch));
+                p.addLast(new ChannelHandlerAdapter() {
+                    @Override
+                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+                        if (cause.getCause() instanceof SSLHandshakeException) {
+                            clientException = cause.getCause();
+                            clientLatch.countDown();
+                        } else {
+                            ctx.fireExceptionCaught(cause);
+                        }
+                    }
+                });
+            }
+        });
+
+        serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel();
+        int port = ((InetSocketAddress) serverChannel.localAddress()).getPort();
+
+        ChannelFuture ccf = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port));
+        assertTrue(ccf.awaitUninterruptibly().isSuccess());
+        clientChannel = ccf.channel();
+    }
+
     private void runTest() throws Exception {
         runTest(APPLICATION_LEVEL_PROTOCOL);
     }
@@ -395,8 +523,10 @@ private void runTest(String expectedApplicationProtocol) throws Exception {
         try {
             writeAndVerifyReceived(clientMessage.retain(), clientChannel, serverLatch, serverReceiver);
             writeAndVerifyReceived(serverMessage.retain(), serverConnectedChannel, clientLatch, clientReceiver);
-            verifyApplicationLevelProtocol(clientChannel, expectedApplicationProtocol);
-            verifyApplicationLevelProtocol(serverConnectedChannel, expectedApplicationProtocol);
+            if (expectedApplicationProtocol != null) {
+                verifyApplicationLevelProtocol(clientChannel, expectedApplicationProtocol);
+                verifyApplicationLevelProtocol(serverConnectedChannel, expectedApplicationProtocol);
+            }
         } finally {
             clientMessage.release();
             serverMessage.release();
diff --git a/handler/src/test/java/io/netty/handler/ssl/JdkSslServerContextTest.java b/handler/src/test/java/io/netty/handler/ssl/JdkSslServerContextTest.java
index c196b40f28a..9eccd91b03d 100644
--- a/handler/src/test/java/io/netty/handler/ssl/JdkSslServerContextTest.java
+++ b/handler/src/test/java/io/netty/handler/ssl/JdkSslServerContextTest.java
@@ -31,6 +31,14 @@ public void testJdkSslServerWithEncryptedPrivateKey() throws SSLException {
         new JdkSslServerContext(crtFile, keyFile, "12345");
     }
 
+    @Test
+    public void testJdkSslServerWithEncryptedPrivateKey2() throws SSLException {
+        File keyFile = new File(getClass().getResource("test2_encrypted.pem").getFile());
+        File crtFile = new File(getClass().getResource("test2.crt").getFile());
+
+        new JdkSslServerContext(crtFile, keyFile, "12345");
+    }
+
     @Test
     public void testJdkSslServerWithUnencryptedPrivateKey() throws SSLException {
         File keyFile = new File(getClass().getResource("test_unencrypted.pem").getFile());
diff --git a/handler/src/test/resources/io/netty/handler/ssl/test2.crt b/handler/src/test/resources/io/netty/handler/ssl/test2.crt
new file mode 100644
index 00000000000..7f4b30d446d
--- /dev/null
+++ b/handler/src/test/resources/io/netty/handler/ssl/test2.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC6DCCAdACCQCp0Mn/2UCl2TANBgkqhkiG9w0BAQsFADA2MTQwMgYDVQQDDCtj
+ZmYyNGEwY2I4NGFmNjExZDdhODFjMGI4MDY4OTA2OC5uZXR0eS50ZXN0MB4XDTE0
+MTAxNzE4NDczM1oXDTE0MTExNjE4NDczM1owNjE0MDIGA1UEAwwrY2ZmMjRhMGNi
+ODRhZjYxMWQ3YTgxYzBiODA2ODkwNjgubmV0dHkudGVzdDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALgddI5XJcUK45ONr4QTfZZxbJJeOYKPEWVIWK/P
+Wz6EJXt3hDdpmnaRUKAv4mMIFlxWVkxTqa/dB3hjcm5hPvNgPAUaEWzMtGd32p95
+sJzbxiWvxhf5rqF0n1Zk5KX+EcasiCupNg3TL7gfTSSZfaGSWf460oaCS6WCU4X9
+XTUhys7N5BFM+uQLE048CnkBCO1An980Fau/0+BLXgW+iJC6XWTJbpZ+r7rDpBKl
++HmQQ5tgGlCZcnhmS9bzYT3hoag6JkDoIwbFsVOkwemxZGb8GsGE74/rrzUJ9MdR
+/ETCA2km1na6ESst0/wm0qD3clJahP8xEoaJ+W1TFGizRWkCAwEAATANBgkqhkiG
+9w0BAQsFAAOCAQEAmeGPRWXzu7+f20ZJA/u6WjcmsUhSTtt0YcBNtii4Pm0snIE9
+UyRBGlvS2uFHTilD7MOYOHX6ATlHZAsfegpiPE5jCvE4CzFPpQaVAT/sKNtsWH43
+ZQHn4NK1DAFIVDysO3AGGhL0mub8iWEYHs81+6tSSFlbDFqwYtw7ueerhVLUIaIa
+S0SvtXUVitX2LzMlYCEto2s50fcqOnj8uve/dG8BmiwR1DqqVKkAWAXf8uGhwwD+
+659E3g9vNz6QUchd8K/TIv52i8EDuWu3FElohmfFUXu43A+Z+lbuDrEW3suqTC3y
+0JIa2DfHWA7WTyF4UD32aAC+U6BLIOA6WoPi1Q==
+-----END CERTIFICATE-----
diff --git a/handler/src/test/resources/io/netty/handler/ssl/test2_encrypted.pem b/handler/src/test/resources/io/netty/handler/ssl/test2_encrypted.pem
new file mode 100644
index 00000000000..a17f9dc27eb
--- /dev/null
+++ b/handler/src/test/resources/io/netty/handler/ssl/test2_encrypted.pem
@@ -0,0 +1,29 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIE6jAcBgoqhkiG9w0BDAEDMA4ECCqT2dycwPtCAgIIAASCBMg/Z60Q85kVL5kv
+q8WIIY9tbXo/2Q+6rspxdit9SRd86MV9QRdfZ5Vjwt0JTa+Rd1gMaNK4PySW23bq
+F2+dD0sjVBcE24Qg0h4BcmL+YBdTftBfk7NDH/rHhsew7DZru9fdDvkO9bV3jXIz
+fARW9U7JIfgAi6CfJ8Q1PS7sg6dVtrcjMRIie32x0TSbZrn+h9AaXpLHsC8oXiyY
+BhWe4i9B7PobyJ0r/CTBFhbfUCGwRyHac0+bZXvlcwX9wy3W7jagc6RDlznOpowU
+FP35CQGeKsJ9WD+yy5MU8X8M8v+eeaJk4oX+PSWJX669CxbYocVP/+LUtOXpe+4h
+7yMmVNLUtsgBlY6tNsU0XBQkrqqb+voSxVBEVZ1WTKgLWsE/EiQ2P2GU8Gnr+J6c
+/yHxw0D4q9J3jV40SiuXQlgFwlf8u9FuVjOcGxTidfKXyvNqPKqgkf9QD+7E09q3
+JQoNbI/A8BXrpdx9h87Gt0TblPwVJP2nf5whig9W62R4y9SWybUUNr2MFNkvEfKe
+1QK8isf+HlvIO+VBYi4jof9HkWLwnAszlkpC+k1cOiSjNRn8QyLzsqX7A/VuS6W8
+6kKeND4yRNA4b7rfQqhyGg7gBwiwN+22UF6SKiikX4TB1ZyLdzlbPe0L+X/Gq0Jz
+Kf+8/slgzB5K9WpDtKsARH/lRPAx1rcascvFxMuCJL5O9MO9l4xWDJor71WgPC2N
+KwXxvEW3Kyvs3pSgWc8MC0BKcD9WIAahAlAVmSQBxDNWvJlGTgUVhzPqan7h03Fd
+nWAxSn315ObfK9rjbqUBO9x/nkSZFS9nApmeiWkOIwVzgNfAfb9md07TYyC/rpK3
+nGIsThekqqQULMQaAPmEFqUj6A/0KlpBj1gZwddYvVvEL/MuQO0QBdz4n/OncxYP
+TVoQEqXsndmNQnkuk2Kr4FACV2M9rbr84HJUIZVGGVSM5h80GrRqK03qpTzM8Nkc
+e04R4KDpLDKHm+G4xYZbbraIGXNTkhxTqdNA2FyjJWFurmpQyFay55vC6WBFBVNA
+BGVIqD1/9K3dJJGlpiHyymRCK9YGvflZlSr7dm7PW7PPEthwTijbAHkABOKsFSiu
+xaUj027WIVuDb5FFIAaF3Wmn4GFXvsSH+8L95CQuXGB8J/5Buo+/Hg6S7PeDwrf+
+qNRAfg9vxo+AZOWpWfGEYGHQeX6BxVjdffar9RwL99cele4h2FgBLtIuAXvgLPyx
+b+MIjDliCe1Nqx0PCCuaB1xRnaKiwbl7itDidzI8BUAaFcKxbBH2lpr44+vYPVHb
+70Xrw55RLvrVYKAcaZgryTNOvbRatifJIMg3kf8V++2rwUMoZ+DQfXin/C4S/2/b
+c6I1OvYaGxmI1YiI6qSpOryDSzTNlDEWcdh5feuixiP5RbyaQFswq2fH0hsWWHS4
+OsCeqT0nm5vd1CdUFQJ4Nuh/TTdgCAVKk5yJZJvH2BX77I2d4T0ZRGHLDKUm8P0E
+n6ntrMqLFR+QooONAZg0DTaxvbsCvaupRJCn9NgiwtXyYJKbvf5F8NEOe57NoGwd
+LqQ332mVTuJ1DiqnChLoe7Mz7OY21RsTa/AK5Q/onClvBATrLD0ynK4WiLn4+hGs
+HK5t3audgdnrLxs4UoA=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/handler/src/test/resources/io/netty/handler/ssl/test2_unencrypted.pem b/handler/src/test/resources/io/netty/handler/ssl/test2_unencrypted.pem
new file mode 100644
index 00000000000..209a9c05bec
--- /dev/null
+++ b/handler/src/test/resources/io/netty/handler/ssl/test2_unencrypted.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC4HXSOVyXFCuOT
+ja+EE32WcWySXjmCjxFlSFivz1s+hCV7d4Q3aZp2kVCgL+JjCBZcVlZMU6mv3Qd4
+Y3JuYT7zYDwFGhFszLRnd9qfebCc28Ylr8YX+a6hdJ9WZOSl/hHGrIgrqTYN0y+4
+H00kmX2hkln+OtKGgkulglOF/V01IcrOzeQRTPrkCxNOPAp5AQjtQJ/fNBWrv9Pg
+S14FvoiQul1kyW6Wfq+6w6QSpfh5kEObYBpQmXJ4ZkvW82E94aGoOiZA6CMGxbFT
+pMHpsWRm/BrBhO+P6681CfTHUfxEwgNpJtZ2uhErLdP8JtKg93JSWoT/MRKGiflt
+UxRos0VpAgMBAAECggEAYiTZd/L+oD3AuGwjrp0RKjwGKzPtJiqLlFjvZbB8LCQX
+Muyv3zX8781glDNSU4YBHXGsiP1kC+ofzE3+ttZBz0xyUinmNgAc/rbGJJKi0crZ
+okdDqo4fR9O6CDy6Ib4Azc40vEl0FgSIgHa3EZZ8gL9aF4pVpPwZxP1m9prrr6EP
+SOlJP7rJNA/sTpuy0gz+UAu2Xf53pdkREUW7E2uzIGwrHxQVserN7Xxtft/zT79/
+oIHF09pHfiqE8a2TuVvVavjwV6787PSewFs7j8iKId9bpo1O7iqvj0UKOE+/63Lf
+1pWRn7lRGS9ACw8EoyTY/M0njUbDEfaObJUzt08pjQKBgQDevZLRQjbGDtKOfQe6
+PKb/6PeFEE466NPFKH1bEz26VmC5vzF8U7lk71S11Dma51+vbOENzS5VlqOWqO+N
+CyXTzb8a0rHXXUEP4+V6CazesTOEoBKViDswt2ffJfQYoCOFfKrcKq0j1Ps8Svhq
+yzcMjAfX8eKIDWxK3qk+09SBtwKBgQDTm2Te4ENYwV5be+Z5L5See8gHNU5w3RtU
+koO54TYBeJOTsTTtGDqEg60MoWIcx69OAJlHwTp5nPV5fhrjB8I9WUmI+2sPK7sU
+OmhV/QzPjr6HW7fpbvbZ6fT+/Ay3aREa+qsJMypXsoqML1/fAeBno3hvHQt5Neog
+leu3m0/x3wKBgQCCc8b8FeqcfuvkleejtIgeU2Q8I3ud1uTIkNkyMQezDYni385s
+wWBQdDdJsvz181LAHGWGvsfHSs2OnGyIT6Ic9WBaplGQD8beNpwcqHP9jQzePR4F
+Q99evdvw/nqCva9wK76p6bizxrZJ7qKlcVVRXOXvHHSPOEVXaCb5a/kG6wKBgGN6
+2G8XC1I8hfmIRA+Q2NOw6ZbJ7riMmf6mapsGT3ddkjOKyZD1JP2LUd1wOUnCbp3D
+FkxvgOgPbC/Toxw8V4qz4Sgu2mPlcSvPUaGrN0yUlOnZqpppek9z96OwJuJK2KnQ
+Unweu7dCznOdCfszTKYsacAC7ZPsTsdG8+v7bhgNAoGBAL8wlTp3tfQ2iuGDnQaf
+268BBUtqp2qPlGPXCdkc5XXbnHXLFY/UYGw27Vh+UNW8UORTFYEb8XPvUxB4q2Mx
+8ZZdcjFB1J4dM2+KGr51CEuzzpFuhFU8Nn4D/hcfYNKg733gTeSoI0Gs2Y9R+bDo
++cA9UxmyFSgS+Dq/7BOmPCDI
+-----END PRIVATE KEY-----
