blob: aad716a62c347a56999e111b35fcf62a69d364df [file] [log] [blame]
/*
*/
package org.apache.tomcat.lite.io.jsse;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyManagementException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
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.PKCS8EncodedKeySpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509TrustManager;
import org.apache.tomcat.lite.io.BBuffer;
import org.apache.tomcat.lite.io.DumpChannel;
import org.apache.tomcat.lite.io.IOChannel;
import org.apache.tomcat.lite.io.IOConnector;
import org.apache.tomcat.lite.io.SocketConnector;
import org.apache.tomcat.lite.io.SslProvider;
import org.apache.tomcat.lite.io.WrappedException;
import org.apache.tomcat.lite.io.IOConnector.ConnectedCallback;
public class JsseSslProvider implements SslProvider {
/**
* TODO: option to require validation.
* TODO: remember cert signature. This is needed to support self-signed
* certs, like those used by the test.
*
*/
public static class BasicTrustManager implements X509TrustManager {
private X509Certificate[] chain;
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
this.chain = chain;
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
this.chain = chain;
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
public static TrustManager[] trustAllCerts = new TrustManager[] {
new BasicTrustManager() };
static String[] enabledCiphers;
static final boolean debug = false;
IOConnector net;
private KeyManager[] keyManager;
SSLContext sslCtx;
boolean server;
private TrustManager[] trustManagers;
public AtomicInteger handshakeCount = new AtomicInteger();
public AtomicInteger handshakeOk = new AtomicInteger();
public AtomicInteger handshakeErr = new AtomicInteger();
public AtomicInteger handshakeTime = new AtomicInteger();
Executor handshakeExecutor = Executors.newCachedThreadPool();
static int id = 0;
public JsseSslProvider() {
}
public static void setEnabledCiphers(String[] enabled) {
enabledCiphers = enabled;
}
public void start() {
}
SSLContext getSSLContext() {
if (sslCtx == null) {
try {
sslCtx = SSLContext.getInstance("TLS");
if (trustManagers == null) {
trustManagers =
new TrustManager[] {new BasicTrustManager()};
}
sslCtx.init(keyManager, trustManagers, null);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (KeyManagementException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return sslCtx;
}
public IOConnector getNet() {
if (net == null) {
getSSLContext();
net = new SocketConnector();
}
return net;
}
@Override
public IOChannel channel(IOChannel net, String host, int port) throws IOException {
if (debug) {
net = DumpChannel.wrap("S-ENC-" + id, net);
}
SslChannel ch = new SslChannel()
.setTarget(host, port)
.setSslContext(getSSLContext())
.setSslProvider(this);
net.setHead(ch);
return ch;
}
@Override
public SslChannel serverChannel(IOChannel net) throws IOException {
SslChannel ch = new SslChannel()
.setSslContext(getSSLContext())
.setSslProvider(this).withServer();
ch.setSink(net);
return ch;
}
public void acceptor(final ConnectedCallback sc, CharSequence port, Object extra)
throws IOException {
getNet().acceptor(new ConnectedCallback() {
@Override
public void handleConnected(IOChannel ch) throws IOException {
IOChannel first = ch;
if (debug) {
first = DumpChannel.wrap("S-ENC-" + id, ch);
}
IOChannel sslch = serverChannel(first);
sslch.setSink(first);
first.setHead(sslch);
if (debug) {
sslch = DumpChannel.wrap("S-CLR-" + id, sslch);
id++;
}
sc.handleConnected(sslch);
}
}, port, extra);
}
public void connect(final String host, final int port, final ConnectedCallback sc)
throws IOException {
getNet().connect(host, port, new ConnectedCallback() {
@Override
public void handleConnected(IOChannel ch) throws IOException {
IOChannel first = ch;
if (debug) {
first = DumpChannel.wrap("ENC-" + id, first);
}
IOChannel sslch = channel(first, host, port);
// first.setHead(sslch);
if (debug) {
sslch = DumpChannel.wrap("CLR-" + id, sslch);
id++;
}
sc.handleConnected(sslch);
}
});
}
public JsseSslProvider withKeyManager(KeyManager[] kms) {
this.keyManager = kms;
return this;
}
public JsseSslProvider setKeystoreFile(String file, String pass) throws IOException {
return setKeystore(new FileInputStream(file), pass);
}
public JsseSslProvider setKeystoreResource(String res, String pass) throws IOException {
return setKeystore(this.getClass().getClassLoader().getResourceAsStream(res),
pass);
}
public JsseSslProvider setKeystore(InputStream file, String pass) {
char[] passphrase = pass.toCharArray();
KeyStore ks;
try {
String type = KeyStore.getDefaultType();
System.err.println("Keystore: " + type);
// Java: JKS
// Android: BKS
ks = KeyStore.getInstance(type);
ks.load(file, passphrase);
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, passphrase);
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
keyManager = kmf.getKeyManagers();
trustManagers = tmf.getTrustManagers();
} catch (KeyStoreException e) {
// No JKS keystore ?
// TODO Auto-generated catch block
}catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return this;
}
public JsseSslProvider setKeys(X509Certificate cert, PrivateKey privKey) {
keyManager = new KeyManager[] {
new TestKeyManager(cert, privKey)
};
return this;
}
public JsseSslProvider setKeyFiles(String certPem, String keyFile)
throws IOException {
return this;
}
public JsseSslProvider setKeyRes(String certPem, String keyFile)
throws IOException {
setKeys(this.getClass().getClassLoader().getResourceAsStream(certPem),
this.getClass().getClassLoader().getResourceAsStream(keyFile));
return this;
}
private void setKeys(InputStream certPem,
InputStream keyDer) throws IOException {
BBuffer keyB = BBuffer.allocate(2048);
keyB.readAll(keyDer);
byte[] key = new byte[keyB.remaining()];
keyB.getByteBuffer().get(key);
setKeys(certPem, key);
}
public JsseSslProvider setKeys(String certPem, byte[] keyBytes) throws IOException{
InputStream is = new ByteArrayInputStream(certPem.getBytes());
return setKeys(is, keyBytes);
}
/**
* Initialize using a PEM certificate and key bytes.
* ( TODO: base64 dep to set the key as PEM )
*
* openssl genrsa 1024 > host.key
* openssl pkcs8 -topk8 -nocrypt -in host.key -inform PEM
* -out host.der -outform DER
* openssl req -new -x509 -nodes -sha1 -days 365 -key host.key > host.cert
*
*/
public JsseSslProvider setKeys(InputStream certPem, byte[] keyBytes) throws IOException{
// convert key
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec(keyBytes);
PrivateKey priv = kf.generatePrivate (keysp);
// Convert cert pem to certificate
CertificateFactory cf = CertificateFactory.getInstance("X.509");
final X509Certificate cert = (X509Certificate) cf.generateCertificate(certPem);
setKeys(cert, priv);
} catch (Throwable t) {
throw new WrappedException(t);
}
return this;
}
public class TestKeyManager extends X509ExtendedKeyManager {
X509Certificate cert;
PrivateKey privKey;
public TestKeyManager(X509Certificate cert2, PrivateKey privKey2) {
cert = cert2;
privKey = privKey2;
}
public String chooseEngineClientAlias(String[] keyType,
java.security.Principal[] issuers, javax.net.ssl.SSLEngine engine) {
return "client";
}
public String chooseEngineServerAlias(String keyType,
java.security.Principal[] issuers, javax.net.ssl.SSLEngine engine) {
return "server";
}
public String chooseClientAlias(String[] keyType,
Principal[] issuers, Socket socket) {
return "client";
}
public String chooseServerAlias(String keyType,
Principal[] issuers, Socket socket) {
return "server";
}
public X509Certificate[] getCertificateChain(String alias) {
return new X509Certificate[] {cert};
}
public String[] getClientAliases(String keyType, Principal[] issuers) {
return null;
}
public PrivateKey getPrivateKey(String alias) {
return privKey;
}
public String[] getServerAliases(String keyType, Principal[] issuers) {
return null;
}
}
// TODO: add a mode that trust a defined list of certs, like SSH
/**
* Make URLConnection accept all certificates.
* Use only for testing !
*/
public static void testModeURLConnection() {
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, JsseSslProvider.trustAllCerts, null);
javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(
sc.getSocketFactory());
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new HostnameVerifier() {
@Override
public boolean verify(String hostname,
SSLSession session) {
try {
Certificate[] certs = session.getPeerCertificates();
// TODO...
// see org/apache/http/conn/ssl/AbstractVerifier
} catch (SSLPeerUnverifiedException e) {
e.printStackTrace();
}
return true;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
// Utilities
public static byte[] getPrivateKeyFromStore(String file, String pass)
throws Exception {
KeyStore store = KeyStore.getInstance("JKS");
store.load(new FileInputStream(file), pass.toCharArray());
Key key = store.getKey("tomcat", "changeit".toCharArray());
PrivateKey pk = (PrivateKey) key;
byte[] encoded = pk.getEncoded();
return encoded;
}
public static byte[] getCertificateFromStore(String file, String pass)
throws Exception {
KeyStore store = KeyStore.getInstance("JKS");
store.load(new FileInputStream(file), pass.toCharArray());
Certificate certificate = store.getCertificate("tomcat");
return certificate.getEncoded();
}
public static KeyPair generateRsaOrDsa(boolean rsa) throws Exception {
if (rsa) {
KeyPairGenerator keyPairGen =
KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(1024);
RSAKeyGenParameterSpec keySpec = new RSAKeyGenParameterSpec(1024,
RSAKeyGenParameterSpec.F0);
keyPairGen.initialize(keySpec);
KeyPair rsaKeyPair = keyPairGen.generateKeyPair();
return rsaKeyPair;
} else {
KeyPairGenerator keyPairGen =
KeyPairGenerator.getInstance("DSA");
keyPairGen.initialize(1024);
KeyPair pair = keyPairGen.generateKeyPair();
return pair;
}
}
/**
* I know 2 ways to generate certs:
* - keytool
* - openssl req -x509 -nodes -days 365 \
* -newkey rsa:1024 -keyout mycert.pem -out mycert.pem
* openssl s_server -accept 9443 -cert mycert.pem -debug -msg -state -www
*/
}