SSL JDK Mutual Authentication
Motivation: Netty currently does not support creating SslContext objects that support mutual authentication. Modifications: -Modify the SslContext interface to support mutual authentication for JDK and OpenSSL -Provide an implementation of mutual authentication for JDK -Add unit tests to support new feature Result: Netty SslContext interface supports mutual authentication and JDK providers have an implementation.
This commit is contained in:
parent
f8af84d599
commit
e74c8edba3
@ -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 final class JdkSslClientContext extends JdkSslContext {
|
||||
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 final class JdkSslClientContext extends JdkSslContext {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.HashSet;
|
||||
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 @@ public abstract class JdkSslContext extends SslContext {
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
@ -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 final class JdkSslServerContext extends JdkSslContext {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (certChainFile == null) {
|
||||
throw new NullPointerException("certChainFile");
|
||||
}
|
||||
if (keyFile == null) {
|
||||
throw new NullPointerException("keyFile");
|
||||
}
|
||||
|
||||
if (keyPassword == null) {
|
||||
keyPassword = "";
|
||||
}
|
||||
|
||||
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
|
||||
if (algorithm == null) {
|
||||
algorithm = "SunX509";
|
||||
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 final class JdkSslServerContext extends JdkSslContext {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ import io.netty.channel.ChannelPipeline;
|
||||
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 abstract class SslContext {
|
||||
* {@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 abstract class SslContext {
|
||||
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 abstract class SslContext {
|
||||
*
|
||||
* @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() { }
|
||||
|
@ -41,6 +41,7 @@ import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
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 class JdkSslEngineTest {
|
||||
}
|
||||
}
|
||||
|
||||
@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 class JdkSslEngineTest {
|
||||
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 @@ public class JdkSslEngineTest {
|
||||
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();
|
||||
|
@ -31,6 +31,14 @@ public class JdkSslServerContextTest {
|
||||
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());
|
||||
|
18
handler/src/test/resources/io/netty/handler/ssl/test2.crt
Normal file
18
handler/src/test/resources/io/netty/handler/ssl/test2.crt
Normal file
@ -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-----
|
@ -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-----
|
@ -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-----
|
Loading…
Reference in New Issue
Block a user