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:
Norman Maurer 2014-10-21 22:14:34 +02:00 committed by Norman Maurer
parent 473c23aec9
commit 393e3ea383
8 changed files with 421 additions and 180 deletions

View File

@ -23,12 +23,7 @@ import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory; import io.netty.util.internal.logging.InternalLoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.NoSuchPaddingException; 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.KeyManagerFactory;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
@ -38,7 +33,6 @@ import javax.security.auth.x500.X500Principal;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyException; import java.security.KeyException;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.KeyStore; import java.security.KeyStore;
@ -401,39 +395,4 @@ public abstract class JdkSslContext extends SslContext {
return trustManagerFactory; 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);
}
} }

View File

@ -17,6 +17,7 @@
package io.netty.handler.ssl; package io.netty.handler.ssl;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;

View File

@ -15,7 +15,12 @@
*/ */
package io.netty.handler.ssl; package io.netty.handler.ssl;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator; 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.SSL;
import org.apache.tomcat.jni.SSLContext; import org.apache.tomcat.jni.SSLContext;
@ -24,17 +29,21 @@ import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.security.KeyStore; 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; import java.util.List;
/** /**
* A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. * A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
*/ */
public final class OpenSslClientContext extends OpenSslContext { public final class OpenSslClientContext extends OpenSslContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslClientContext.class);
private final X509TrustManager[] managers;
/** /**
* Creates a new instance. * Creates a new instance.
@ -116,32 +125,31 @@ public final class OpenSslClientContext extends OpenSslContext {
} }
SSLContext.setVerify(ctx, SSL.SSL_VERIFY_NONE, VERIFY_DEPTH); SSLContext.setVerify(ctx, SSL.SSL_VERIFY_NONE, VERIFY_DEPTH);
// check if verification should take place or not. try {
if (trustManagerFactory != null) { // Set up trust manager factory to use our key store.
try { if (trustManagerFactory == null) {
if (certChainFile == null) { trustManagerFactory = TrustManagerFactory.getInstance(
trustManagerFactory.init((KeyStore) null); TrustManagerFactory.getDefaultAlgorithm());
} else {
initTrustManagerFactory(certChainFile, trustManagerFactory);
}
} catch (Exception e) {
throw new SSLException("failed to initialize the client-side SSL context", e);
} }
TrustManager[] tms = trustManagerFactory.getTrustManagers(); initTrustManagerFactory(certChainFile, trustManagerFactory);
if (tms == null || tms.length == 0) { final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers());
managers = null;
} else { SSLContext.setCertVerifyCallback(ctx, new CertificateVerifier() {
List<X509TrustManager> managerList = new ArrayList<X509TrustManager>(tms.length); @Override
for (TrustManager tm: tms) { public boolean verify(long ssl, byte[][] chain, String auth) {
if (tm instanceof X509TrustManager) { X509Certificate[] peerCerts = certificates(chain);
managerList.add((X509TrustManager) tm); 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()]); });
} } catch (Exception e) {
} else { throw new SSLException("unable to setup trustmanager", e);
managers = null; }
}
} }
success = true; success = true;
} finally { } 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 @Override
public SSLEngine newEngine(ByteBufAllocator alloc) { public SSLEngine newEngine(ByteBufAllocator alloc) {
List<String> protos = applicationProtocolNegotiator().protocols(); List<String> protos = applicationProtocolNegotiator().protocols();
if (protos.isEmpty()) { if (protos.isEmpty()) {
return new OpenSslEngine(ctx, alloc, null, isClient(), managers); return new OpenSslEngine(ctx, alloc, null, isClient());
} else { } else {
return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), managers); return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient());
} }
} }
} }

View File

@ -25,6 +25,9 @@ import org.apache.tomcat.jni.SSLContext;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -221,6 +224,7 @@ public abstract class OpenSslContext extends SslContext {
public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) { public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/** /**
* Returns the {@code SSL_CTX} object of this context. * 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 * Translate a {@link ApplicationProtocolConfig} object to a
* {@link OpenSslApplicationProtocolNegotiator} object. * {@link OpenSslApplicationProtocolNegotiator} object.
@ -273,7 +294,7 @@ public abstract class OpenSslContext extends SslContext {
* @return The results of the translation * @return The results of the translation
*/ */
static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config, static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config,
boolean isServer) { boolean isServer) {
if (config == null) { if (config == null) {
return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE; return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE;
} }
@ -289,7 +310,8 @@ public abstract class OpenSslContext extends SslContext {
default: default:
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
new StringBuilder("OpenSSL provider does not support ") new StringBuilder("OpenSSL provider does not support ")
.append(config.selectedListenerFailureBehavior()).append(" behavior").toString()); .append(config.selectedListenerFailureBehavior())
.append(" behavior").toString());
} }
} else { } else {
throw new UnsupportedOperationException("OpenSSL provider does not support client mode"); throw new UnsupportedOperationException("OpenSSL provider does not support client mode");

View File

@ -32,17 +32,13 @@ import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLSessionBindingEvent;
import javax.net.ssl.SSLSessionBindingListener; import javax.net.ssl.SSLSessionBindingListener;
import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.X509TrustManager;
import javax.security.cert.X509Certificate; import javax.security.cert.X509Certificate;
import java.io.ByteArrayInputStream; import javax.security.cert.CertificateException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException; import java.nio.ReadOnlyBufferException;
import java.security.Principal; import java.security.Principal;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
@ -130,7 +126,6 @@ public final class OpenSslEngine extends SSLEngine {
private int lastPrimingReadResult; private int lastPrimingReadResult;
private final X509TrustManager[] managers;
private final boolean clientMode; private final boolean clientMode;
private final ByteBufAllocator alloc; private final ByteBufAllocator alloc;
private final String fallbackApplicationProtocol; private final String fallbackApplicationProtocol;
@ -146,7 +141,7 @@ public final class OpenSslEngine extends SSLEngine {
*/ */
@Deprecated @Deprecated
public OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol) { 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 * @param clientMode {@code true} if this is used for clients, {@code false} otherwise
*/ */
OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol, OpenSslEngine(long sslCtx, ByteBufAllocator alloc, String fallbackApplicationProtocol,
boolean clientMode, X509TrustManager[] managers) { boolean clientMode) {
OpenSsl.ensureAvailability(); OpenSsl.ensureAvailability();
if (sslCtx == 0) { if (sslCtx == 0) {
throw new NullPointerException("sslContext"); throw new NullPointerException("sslContext");
@ -171,11 +166,6 @@ public final class OpenSslEngine extends SSLEngine {
networkBIO = SSL.makeNetworkBIO(ssl); networkBIO = SSL.makeNetworkBIO(ssl);
this.fallbackApplicationProtocol = fallbackApplicationProtocol; this.fallbackApplicationProtocol = fallbackApplicationProtocol;
this.clientMode = clientMode; 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 // In handshake or close_notify stages, check if call to wrap was made
// without regard to the handshake status. // without regard to the handshake status.
SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus(true); SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_UNWRAP) { if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_UNWRAP) {
return new SSLEngineResult(getEngineStatus(), NEED_UNWRAP, 0, 0); return new SSLEngineResult(getEngineStatus(), NEED_UNWRAP, 0, 0);
@ -407,7 +397,7 @@ public final class OpenSslEngine extends SSLEngine {
shutdown(); 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 // 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(); int capacity = dst.remaining();
if (capacity < pendingNet) { if (capacity < pendingNet) {
return new SSLEngineResult( 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 // 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); 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 @Override
@ -490,7 +480,7 @@ public final class OpenSslEngine extends SSLEngine {
// In handshake or close_notify stages, check if call to unwrap was made // In handshake or close_notify stages, check if call to unwrap was made
// without regard to the handshake status. // without regard to the handshake status.
SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus(true); SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_WRAP) { if ((!handshakeFinished || engineClosed) && handshakeStatus == NEED_WRAP) {
return new SSLEngineResult(getEngineStatus(), NEED_WRAP, 0, 0); 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? // Do we have enough room in dsts to write decrypted data?
if (capacity < pendingApp) { 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 // Write decrypted data to dsts buffers
@ -575,7 +565,7 @@ public final class OpenSslEngine extends SSLEngine {
closeInbound(); closeInbound();
} }
return new SSLEngineResult(getEngineStatus(), handshakeStatus(true), bytesConsumed, bytesProduced); return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
} }
@Override @Override
@ -679,33 +669,27 @@ public final class OpenSslEngine extends SSLEngine {
if (chain == null && clientCert == null) { if (chain == null && clientCert == null) {
throw new SSLPeerUnverifiedException("peer not verified"); throw new SSLPeerUnverifiedException("peer not verified");
} }
try { int len = 0;
int len = 0; if (chain != null) {
if (chain != null) { len += chain.length;
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 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 @Override
@ -851,7 +835,7 @@ public final class OpenSslEngine extends SSLEngine {
for (int i = 0; i < peerCerts.length; i++) { for (int i = 0; i < peerCerts.length; i++) {
try { try {
peerCerts[i] = X509Certificate.getInstance(chain[i]); peerCerts[i] = X509Certificate.getInstance(chain[i]);
} catch (javax.security.cert.CertificateException e) { } catch (CertificateException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
} }
@ -997,16 +981,6 @@ public final class OpenSslEngine extends SSLEngine {
@Override @Override
public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() { 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) { if (accepted == 0 || destroyed != 0) {
return NOT_HANDSHAKING; return NOT_HANDSHAKING;
} }
@ -1037,10 +1011,6 @@ public final class OpenSslEngine extends SSLEngine {
} else { } else {
this.applicationProtocol = null; this.applicationProtocol = null;
} }
if (verify && managers != null) {
peerCerts = initPeerCertChain();
verifyCertificates(managers, peerCerts, cipher);
}
return FINISHED; return FINISHED;
} }
@ -1131,31 +1101,6 @@ public final class OpenSslEngine extends SSLEngine {
return false; 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 @Override
protected void finalize() throws Throwable { protected void finalize() throws Throwable {
super.finalize(); super.finalize();

View File

@ -15,13 +15,29 @@
*/ */
package io.netty.handler.ssl; package io.netty.handler.ssl;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator; 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.SSL;
import org.apache.tomcat.jni.SSLContext; import org.apache.tomcat.jni.SSLContext;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.File; 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 java.util.List;
import static io.netty.util.internal.ObjectUtil.*; 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. * A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
*/ */
public final class OpenSslServerContext extends OpenSslContext { public final class OpenSslServerContext extends OpenSslContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSslServerContext.class);
/** /**
* Creates a new instance. * Creates a new instance.
@ -166,6 +183,62 @@ public final class OpenSslServerContext extends OpenSslContext {
} catch (Exception e) { } catch (Exception e) {
throw new SSLException("failed to set certificate: " + certChainFile + " and " + keyFile, 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; success = true;
} finally { } finally {
@ -175,16 +248,13 @@ public final class OpenSslServerContext extends OpenSslContext {
} }
} }
/**
* Returns a new server-side {@link javax.net.ssl.SSLEngine} with the current configuration.
*/
@Override @Override
public SSLEngine newEngine(ByteBufAllocator alloc) { public SSLEngine newEngine(ByteBufAllocator alloc) {
List<String> protos = applicationProtocolNegotiator().protocols(); List<String> protos = applicationProtocolNegotiator().protocols();
if (protos.isEmpty()) { if (protos.isEmpty()) {
return new OpenSslEngine(ctx, alloc, null, isClient(), null); return new OpenSslEngine(ctx, alloc, null, isClient());
} else { } else {
return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient(), null); return new OpenSslEngine(ctx, alloc, protos.get(protos.size() - 1), isClient());
} }
} }
} }

View File

@ -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;
}
}

View File

@ -27,6 +27,12 @@ import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory; 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.SSLContext;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
@ -35,12 +41,16 @@ import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.List; import java.util.List;
/** /**
@ -723,23 +733,38 @@ public abstract class SslContext {
return new SslHandler(engine); return new SslHandler(engine);
} }
static void initTrustManagerFactory(File certChainFile, TrustManagerFactory trustManagerFactory) /**
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { * Generates a key specification for an (encrypted) private key.
KeyStore ks = KeyStore.getInstance("JKS"); *
ks.load(null, null); * @param password characters, if {@code null} or empty an unencrypted key is assumed
ByteBuf[] certs = PemReader.readCertificates(certChainFile); * @param key bytes of the DER encoded private key
try { *
for (ByteBuf buf: certs) { * @return a key specification
X509Certificate cert = (X509Certificate) X509_CERT_FACTORY.generateCertificate( *
new ByteBufInputStream(buf)); * @throws IOException if parsing {@code key} fails
X500Principal principal = cert.getSubjectX500Principal(); * @throws NoSuchAlgorithmException if the algorithm used to encrypt {@code key} is unkown
ks.setCertificateEntry(principal.getName("RFC2253"), cert); * @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
} finally { * @throws InvalidKeyException if the decryption key based on {@code password} cannot be used to decrypt
for (ByteBuf buf: certs) { * {@code key}
buf.release(); * @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);
} }
} }