Use TrustManager for certificate verification
Motivation: To make OpenSsl*Context a drop in replacement for JdkSsl*Context we need to use TrustManager. Modifications: Correctly hook in the TrustManager Result: Better compatibility
This commit is contained in:
parent
deb1dfb5b0
commit
8eddeb1ddd
@ -23,12 +23,7 @@ import io.netty.util.internal.EmptyArrays;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
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;
|
||||
@ -38,7 +33,6 @@ import javax.security.auth.x500.X500Principal;
|
||||
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;
|
||||
@ -401,39 +395,4 @@ public abstract class JdkSslContext extends SslContext {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package io.netty.handler.ssl;
|
||||
import java.io.File;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLException;
|
||||
|
@ -15,7 +15,12 @@
|
||||
*/
|
||||
package io.netty.handler.ssl;
|
||||
|
||||
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 org.apache.tomcat.jni.CertificateVerifier;
|
||||
import org.apache.tomcat.jni.SSL;
|
||||
import org.apache.tomcat.jni.SSLContext;
|
||||
|
||||
@ -24,17 +29,21 @@ import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.util.ArrayList;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
|
||||
*/
|
||||
public final class OpenSslClientContext extends OpenSslContext {
|
||||
|
||||
private final X509TrustManager[] managers;
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslClientContext.class);
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
@ -116,32 +125,31 @@ public final class OpenSslClientContext extends OpenSslContext {
|
||||
}
|
||||
SSLContext.setVerify(ctx, SSL.SSL_VERIFY_NONE, VERIFY_DEPTH);
|
||||
|
||||
// check if verification should take place or not.
|
||||
if (trustManagerFactory != null) {
|
||||
try {
|
||||
if (certChainFile == null) {
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
} else {
|
||||
initTrustManagerFactory(certChainFile, trustManagerFactory);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new SSLException("failed to initialize the client-side SSL context", e);
|
||||
try {
|
||||
// Set up trust manager factory to use our key store.
|
||||
if (trustManagerFactory == null) {
|
||||
trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
}
|
||||
TrustManager[] tms = trustManagerFactory.getTrustManagers();
|
||||
if (tms == null || tms.length == 0) {
|
||||
managers = null;
|
||||
} else {
|
||||
List<X509TrustManager> managerList = new ArrayList<X509TrustManager>(tms.length);
|
||||
for (TrustManager tm: tms) {
|
||||
if (tm instanceof X509TrustManager) {
|
||||
managerList.add((X509TrustManager) tm);
|
||||
initTrustManagerFactory(certChainFile, trustManagerFactory);
|
||||
final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
|
||||
|
||||
SSLContext.setCertVerifyCallback(ctx, new CertificateVerifier() {
|
||||
@Override
|
||||
public boolean verify(long ssl, byte[][] chain, String auth) {
|
||||
X509Certificate[] peerCerts = certificates(chain);
|
||||
try {
|
||||
manager.checkServerTrusted(peerCerts, auth);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.debug("verification of certificate failed", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
managers = managerList.toArray(new X509TrustManager[managerList.size()]);
|
||||
}
|
||||
} else {
|
||||
managers = null;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new SSLException("unable to setup trustmanager", e);
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
} finally {
|
||||
@ -151,13 +159,35 @@ public final class OpenSslClientContext extends OpenSslContext {
|
||||
}
|
||||
}
|
||||
|
||||
private static void initTrustManagerFactory(File certChainFile, TrustManagerFactory trustManagerFactory)
|
||||
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
|
||||
KeyStore ks = KeyStore.getInstance("JKS");
|
||||
ks.load(null, null);
|
||||
if (certChainFile != null) {
|
||||
ByteBuf[] certs = PemReader.readCertificates(certChainFile);
|
||||
try {
|
||||
for (ByteBuf buf: certs) {
|
||||
X509Certificate cert = (X509Certificate) X509_CERT_FACTORY.generateCertificate(
|
||||
new ByteBufInputStream(buf));
|
||||
X500Principal principal = cert.getSubjectX500Principal();
|
||||
ks.setCertificateEntry(principal.getName("RFC2253"), cert);
|
||||
}
|
||||
} finally {
|
||||
for (ByteBuf buf: certs) {
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
trustManagerFactory.init(ks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngine newEngine(ByteBufAllocator alloc) {
|
||||
List<String> protos = applicationProtocolNegotiator().protocols();
|
||||
if (protos.isEmpty()) {
|
||||
return new OpenSslEngine(ctx, alloc, null, isClient(), managers);
|
||||
return new OpenSslEngine(ctx, alloc, null, isClient());
|
||||
} else {
|
||||
return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), managers);
|
||||
return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,9 @@ import org.apache.tomcat.jni.SSLContext;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -221,6 +224,7 @@ public abstract class OpenSslContext extends SslContext {
|
||||
public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code SSL_CTX} object of this context.
|
||||
*/
|
||||
@ -265,6 +269,23 @@ public abstract class OpenSslContext extends SslContext {
|
||||
}
|
||||
}
|
||||
|
||||
protected static X509Certificate[] certificates(byte[][] chain) {
|
||||
X509Certificate[] peerCerts = new X509Certificate[chain.length];
|
||||
for (int i = 0; i < peerCerts.length; i++) {
|
||||
peerCerts[i] = new OpenSslX509Certificate(chain[i]);
|
||||
}
|
||||
return peerCerts;
|
||||
}
|
||||
|
||||
protected static X509TrustManager chooseTrustManager(TrustManager[] managers) {
|
||||
for (TrustManager m: managers) {
|
||||
if (m instanceof X509TrustManager) {
|
||||
return (X509TrustManager) m;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("no X509TrustManager found");
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a {@link ApplicationProtocolConfig} object to a
|
||||
* {@link OpenSslApplicationProtocolNegotiator} object.
|
||||
@ -273,7 +294,7 @@ public abstract class OpenSslContext extends SslContext {
|
||||
* @return The results of the translation
|
||||
*/
|
||||
static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config,
|
||||
boolean isServer) {
|
||||
boolean isServer) {
|
||||
if (config == null) {
|
||||
return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE;
|
||||
}
|
||||
@ -289,7 +310,8 @@ public abstract class OpenSslContext extends SslContext {
|
||||
default:
|
||||
throw new UnsupportedOperationException(
|
||||
new StringBuilder("OpenSSL provider does not support ")
|
||||
.append(config.selectedListenerFailureBehavior()).append(" behavior").toString());
|
||||
.append(config.selectedListenerFailureBehavior())
|
||||
.append(" behavior").toString());
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException("OpenSSL provider does not support client mode");
|
||||
|
@ -32,17 +32,13 @@ import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSessionBindingEvent;
|
||||
import javax.net.ssl.SSLSessionBindingListener;
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.security.cert.X509Certificate;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import javax.security.cert.CertificateException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ReadOnlyBufferException;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
@ -130,7 +126,6 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
|
||||
private int lastPrimingReadResult;
|
||||
|
||||
private final X509TrustManager[] managers;
|
||||
private final boolean clientMode;
|
||||
private final ByteBufAllocator alloc;
|
||||
private final String fallbackApplicationProtocol;
|
||||
@ -146,7 +141,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
*/
|
||||
@Deprecated
|
||||
public OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol) {
|
||||
this(sslCtx, alloc, fallbackApplicationProtocol, false, null);
|
||||
this(sslCtx, alloc, fallbackApplicationProtocol, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,7 +152,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
* @param clientMode {@code true} if this is used for clients, {@code false} otherwise
|
||||
*/
|
||||
OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol,
|
||||
boolean clientMode, X509TrustManager[] managers) {
|
||||
boolean clientMode) {
|
||||
OpenSsl.ensureAvailability();
|
||||
if (sslCtx == 0) {
|
||||
throw new NullPointerException("sslContext");
|
||||
@ -171,11 +166,6 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
networkBIO = SSL.makeNetworkBIO(ssl);
|
||||
this.fallbackApplicationProtocol = fallbackApplicationProtocol;
|
||||
this.clientMode = clientMode;
|
||||
if (managers == null || managers.length == 0) {
|
||||
this.managers = null;
|
||||
} else {
|
||||
this.managers = managers;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -375,7 +365,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
|
||||
// In handshake or close_notify stages, check if call to wrap was made
|
||||
// without regard to the handshake status.
|
||||
SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus(true);
|
||||
SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
|
||||
|
||||
if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_UNWRAP) {
|
||||
return new SSLEngineResult(getEngineStatus(), NEED_UNWRAP, 0, 0);
|
||||
@ -407,7 +397,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), 0, bytesProduced);
|
||||
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), 0, bytesProduced);
|
||||
}
|
||||
|
||||
// There was no pending data in the network BIO -- encrypt any application data
|
||||
@ -430,7 +420,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
int capacity = dst.remaining();
|
||||
if (capacity < pendingNet) {
|
||||
return new SSLEngineResult(
|
||||
BUFFER_OVERFLOW, handshakeStatus(true), bytesConsumed, bytesProduced);
|
||||
BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced);
|
||||
}
|
||||
|
||||
// Write the pending data from the network BIO into the dst buffer
|
||||
@ -440,12 +430,12 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
throw new SSLException(e);
|
||||
}
|
||||
|
||||
return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced);
|
||||
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced);
|
||||
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -490,7 +480,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
|
||||
// In handshake or close_notify stages, check if call to unwrap was made
|
||||
// without regard to the handshake status.
|
||||
SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus(true);
|
||||
SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
|
||||
if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_WRAP) {
|
||||
return new SSLEngineResult(getEngineStatus(), NEED_WRAP, 0, 0);
|
||||
}
|
||||
@ -532,7 +522,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
|
||||
// Do we have enough room in dsts to write decrypted data?
|
||||
if (capacity < pendingApp) {
|
||||
return new SSLEngineResult(BUFFER_OVERFLOW, handshakeStatus(true), bytesConsumed, 0);
|
||||
return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, 0);
|
||||
}
|
||||
|
||||
// Write decrypted data to dsts buffers
|
||||
@ -575,7 +565,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
closeInbound();
|
||||
}
|
||||
|
||||
return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced);
|
||||
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -679,33 +669,27 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
if (chain == null && clientCert == null) {
|
||||
throw new SSLPeerUnverifiedException("peer not verified");
|
||||
}
|
||||
try {
|
||||
int len = 0;
|
||||
if (chain != null) {
|
||||
len += chain.length;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
Certificate[] peerCerts;
|
||||
if (clientCert != null) {
|
||||
len++;
|
||||
peerCerts = new Certificate[len];
|
||||
peerCerts[i++] = SslContext.X509_CERT_FACTORY.generateCertificate(
|
||||
new ByteArrayInputStream(clientCert));
|
||||
} else {
|
||||
peerCerts = new Certificate[len];
|
||||
}
|
||||
if (chain != null) {
|
||||
int a = 0;
|
||||
for (; i < peerCerts.length; i++) {
|
||||
peerCerts[i] = SslContext.X509_CERT_FACTORY.generateCertificate(
|
||||
new ByteArrayInputStream(chain[a++]));
|
||||
}
|
||||
}
|
||||
return peerCerts;
|
||||
} catch (CertificateException e) {
|
||||
throw new IllegalStateException(e);
|
||||
int len = 0;
|
||||
if (chain != null) {
|
||||
len += chain.length;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
Certificate[] peerCerts;
|
||||
if (clientCert != null) {
|
||||
len++;
|
||||
peerCerts = new Certificate[len];
|
||||
peerCerts[i++] = new OpenSslX509Certificate(clientCert);
|
||||
} else {
|
||||
peerCerts = new Certificate[len];
|
||||
}
|
||||
if (chain != null) {
|
||||
int a = 0;
|
||||
for (; i < peerCerts.length; i++) {
|
||||
peerCerts[i] = new OpenSslX509Certificate(chain[a++]);
|
||||
}
|
||||
}
|
||||
return peerCerts;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -851,7 +835,7 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
for (int i = 0; i < peerCerts.length; i++) {
|
||||
try {
|
||||
peerCerts[i] = X509Certificate.getInstance(chain[i]);
|
||||
} catch (javax.security.cert.CertificateException e) {
|
||||
} catch (CertificateException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
@ -997,16 +981,6 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
|
||||
@Override
|
||||
public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
|
||||
try {
|
||||
return handshakeStatus(false);
|
||||
} catch (SSLException e) {
|
||||
// Can not happen!
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// No need to synchronize access as it is only called from within synchronized methods
|
||||
private SSLEngineResult.HandshakeStatus handshakeStatus(boolean verify) throws SSLException {
|
||||
if (accepted == 0 || destroyed != 0) {
|
||||
return NOT_HANDSHAKING;
|
||||
}
|
||||
@ -1037,10 +1011,6 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
} else {
|
||||
this.applicationProtocol = null;
|
||||
}
|
||||
if (verify && managers != null) {
|
||||
peerCerts = initPeerCertChain();
|
||||
verifyCertificates(managers, peerCerts, cipher);
|
||||
}
|
||||
return FINISHED;
|
||||
}
|
||||
|
||||
@ -1131,31 +1101,6 @@ public final class OpenSslEngine extends SSLEngine {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void verifyCertificates(X509TrustManager[] managers, Certificate[] peerCerts, String cipher)
|
||||
throws SSLException {
|
||||
|
||||
List<java.security.cert.X509Certificate> certs =
|
||||
new ArrayList<java.security.cert.X509Certificate>(peerCerts.length);
|
||||
for (Certificate c: peerCerts) {
|
||||
if (c instanceof java.security.cert.X509Certificate) {
|
||||
certs.add((java.security.cert.X509Certificate) c);
|
||||
}
|
||||
}
|
||||
java.security.cert.X509Certificate[] certArray =
|
||||
certs.toArray(new java.security.cert.X509Certificate[certs.size()]);
|
||||
for (X509TrustManager tm: managers) {
|
||||
try {
|
||||
if (clientMode) {
|
||||
tm.checkServerTrusted(certArray, cipher);
|
||||
} else {
|
||||
tm.checkClientTrusted(certArray, cipher);
|
||||
}
|
||||
} catch (CertificateException e) {
|
||||
throw new SSLException("peer certificate chain not trusted", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
|
@ -15,13 +15,29 @@
|
||||
*/
|
||||
package io.netty.handler.ssl;
|
||||
|
||||
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 org.apache.tomcat.jni.CertificateVerifier;
|
||||
import org.apache.tomcat.jni.SSL;
|
||||
import org.apache.tomcat.jni.SSLContext;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.File;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
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.List;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.*;
|
||||
@ -30,6 +46,7 @@ import static io.netty.util.internal.ObjectUtil.*;
|
||||
* A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
|
||||
*/
|
||||
public final class OpenSslServerContext extends OpenSslContext {
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslServerContext.class);
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
@ -138,6 +155,62 @@ public final class OpenSslServerContext extends OpenSslContext {
|
||||
} catch (Exception e) {
|
||||
throw new SSLException("failed to set certificate: " + certChainFile + " and " + keyFile, e);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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()]));
|
||||
|
||||
// This mimics the behavior of using SSLContext.init(...);
|
||||
TrustManagerFactory factory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
factory.init((KeyStore) null);
|
||||
final X509TrustManager manager = chooseTrustManager(factory.getTrustManagers());
|
||||
SSLContext.setCertVerifyCallback(ctx, new CertificateVerifier() {
|
||||
@Override
|
||||
public boolean verify(long ssl, byte[][] chain, String auth) {
|
||||
X509Certificate[] peerCerts = certificates(chain);
|
||||
try {
|
||||
manager.checkClientTrusted(peerCerts, auth);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.debug("verification of certificate failed", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new SSLException("unable to setup trustmanager", e);
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
} finally {
|
||||
@ -147,16 +220,13 @@ public final class OpenSslServerContext extends OpenSslContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new server-side {@link javax.net.ssl.SSLEngine} with the current configuration.
|
||||
*/
|
||||
@Override
|
||||
public SSLEngine newEngine(ByteBufAllocator alloc) {
|
||||
List<String> protos = applicationProtocolNegotiator().protocols();
|
||||
if (protos.isEmpty()) {
|
||||
return new OpenSslEngine(ctx, alloc, null, isClient(), null);
|
||||
return new OpenSslEngine(ctx, alloc, null, isClient());
|
||||
} else {
|
||||
return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), null);
|
||||
return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.ssl;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Principal;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
final class OpenSslX509Certificate extends X509Certificate {
|
||||
|
||||
private final byte[] bytes;
|
||||
private X509Certificate wrapped;
|
||||
|
||||
public OpenSslX509Certificate(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkValidity() throws CertificateExpiredException, CertificateNotYetValidException {
|
||||
unwrap().checkValidity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkValidity(Date date) throws CertificateExpiredException, CertificateNotYetValidException {
|
||||
unwrap().checkValidity(date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return unwrap().getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger getSerialNumber() {
|
||||
return unwrap().getSerialNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getIssuerDN() {
|
||||
return unwrap().getIssuerDN();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getSubjectDN() {
|
||||
return unwrap().getSubjectDN();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getNotBefore() {
|
||||
return unwrap().getNotBefore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getNotAfter() {
|
||||
return unwrap().getNotAfter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getTBSCertificate() throws CertificateEncodingException {
|
||||
return unwrap().getTBSCertificate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignature() {
|
||||
return unwrap().getSignature();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSigAlgName() {
|
||||
return unwrap().getSigAlgName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSigAlgOID() {
|
||||
return unwrap().getSigAlgOID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSigAlgParams() {
|
||||
return unwrap().getSigAlgParams();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] getIssuerUniqueID() {
|
||||
return unwrap().getIssuerUniqueID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] getSubjectUniqueID() {
|
||||
return unwrap().getSubjectUniqueID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] getKeyUsage() {
|
||||
return unwrap().getKeyUsage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBasicConstraints() {
|
||||
return unwrap().getBasicConstraints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
return bytes.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(PublicKey key)
|
||||
throws CertificateException, NoSuchAlgorithmException,
|
||||
InvalidKeyException, NoSuchProviderException, SignatureException {
|
||||
unwrap().verify(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(PublicKey key, String sigProvider)
|
||||
throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
|
||||
NoSuchProviderException, SignatureException {
|
||||
unwrap().verify(key, sigProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return unwrap().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getPublicKey() {
|
||||
return unwrap().getPublicKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasUnsupportedCriticalExtension() {
|
||||
return unwrap().hasUnsupportedCriticalExtension();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getCriticalExtensionOIDs() {
|
||||
return unwrap().getCriticalExtensionOIDs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getNonCriticalExtensionOIDs() {
|
||||
return unwrap().getNonCriticalExtensionOIDs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getExtensionValue(String oid) {
|
||||
return unwrap().getExtensionValue(oid);
|
||||
}
|
||||
|
||||
private X509Certificate unwrap() {
|
||||
X509Certificate wrapped = this.wrapped;
|
||||
if (wrapped == null) {
|
||||
try {
|
||||
wrapped = this.wrapped = (X509Certificate) SslContext.X509_CERT_FACTORY.generateCertificate(
|
||||
new ByteArrayInputStream(bytes));
|
||||
} catch (CertificateException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
return wrapped;
|
||||
}
|
||||
}
|
@ -24,6 +24,12 @@ import io.netty.channel.ChannelPipeline;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
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.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
@ -32,12 +38,16 @@ import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
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.List;
|
||||
|
||||
/**
|
||||
@ -565,23 +575,38 @@ public abstract class SslContext {
|
||||
return new SslHandler(engine);
|
||||
}
|
||||
|
||||
static void initTrustManagerFactory(File certChainFile, TrustManagerFactory trustManagerFactory)
|
||||
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
|
||||
KeyStore ks = KeyStore.getInstance("JKS");
|
||||
ks.load(null, null);
|
||||
ByteBuf[] certs = PemReader.readCertificates(certChainFile);
|
||||
try {
|
||||
for (ByteBuf buf: certs) {
|
||||
X509Certificate cert = (X509Certificate) X509_CERT_FACTORY.generateCertificate(
|
||||
new ByteBufInputStream(buf));
|
||||
X500Principal principal = cert.getSubjectX500Principal();
|
||||
ks.setCertificateEntry(principal.getName("RFC2253"), cert);
|
||||
}
|
||||
} finally {
|
||||
for (ByteBuf buf: certs) {
|
||||
buf.release();
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key)
|
||||
throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
|
||||
InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
|
||||
if (password == null || password.length == 0) {
|
||||
return new PKCS8EncodedKeySpec(key);
|
||||
}
|
||||
trustManagerFactory.init(ks);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user