You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by co...@apache.org on 2010/05/25 08:42:26 UTC
svn commit: r947935 - in
/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io:
SslChannel.java SslConnector.java SslProvider.java jsse/
jsse/JsseSslProvider.java jsse/SslChannel.java
Author: costin
Date: Tue May 25 06:42:26 2010
New Revision: 947935
URL: http://svn.apache.org/viewvc?rev=947935&view=rev
Log:
Moved the JSSE code to separate package. Added a bunch of workarounds to support harmony/android, there seems to be a problem with the ciphers.
Probably the code will go away after I add APR support - too many problems, in particular SPDY can't be implemented as it relies on SSL protocol negotiation.
For now it mostly works on android.
Added:
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslProvider.java (with props)
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/JsseSslProvider.java (with props)
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/SslChannel.java (with props)
Removed:
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslConnector.java
Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslProvider.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslProvider.java?rev=947935&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslProvider.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslProvider.java Tue May 25 06:42:26 2010
@@ -0,0 +1,24 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+
+public interface SslProvider {
+
+ public static final String ATT_SSL_CERT = "SslCert";
+ public static final String ATT_SSL_CIPHER = "SslCipher";
+ public static final String ATT_SSL_KEY_SIZE = "SslKeySize";
+ public static final String ATT_SSL_SESSION_ID = "SslSessionId";
+
+ /**
+ * Wrap channel with SSL.
+ *
+ * The result will start a handshake
+ */
+ public IOChannel channel(IOChannel net, String host, int port)
+ throws IOException;
+
+ public IOChannel serverChannel(IOChannel net) throws IOException;
+
+}
Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslProvider.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/JsseSslProvider.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/JsseSslProvider.java?rev=947935&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/JsseSslProvider.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/JsseSslProvider.java Tue May 25 06:42:26 2010
@@ -0,0 +1,476 @@
+/*
+ */
+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) {
+ DumpChannel dch = new DumpChannel("S-ENC-" + id, net);
+ net.setHead(dch);
+ net = dch;
+ }
+ 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) {
+ DumpChannel dch = new DumpChannel("S-ENC-" + id, ch);
+ ch.setHead(dch);
+ first = dch;
+ }
+
+ IOChannel sslch = serverChannel(first);
+ sslch.setSink(first);
+ first.setHead(sslch);
+
+ if (debug) {
+ DumpChannel dch2 = new DumpChannel("S-CLR-" + id, sslch);
+ sslch.setHead(dch2);
+ sslch = dch2;
+ 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) {
+ DumpChannel dch = new DumpChannel("ENC-" + id);
+ ch.setHead(dch);
+ first = dch;
+ }
+
+ IOChannel sslch = channel(first, host, port);
+// first.setHead(sslch);
+
+ if (debug) {
+ DumpChannel dch2 = new DumpChannel("CLR-" + id);
+ sslch.setHead(dch2);
+ sslch = dch2;
+ 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
+ */
+}
\ No newline at end of file
Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/JsseSslProvider.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/SslChannel.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/SslChannel.java?rev=947935&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/SslChannel.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/SslChannel.java Tue May 25 06:42:26 2010
@@ -0,0 +1,633 @@
+/*
+ */
+package org.apache.tomcat.lite.io.jsse;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.SslProvider;
+
+class SslChannel extends IOChannel implements Runnable {
+
+ static Logger log = Logger.getLogger("SSL");
+
+ static ByteBuffer EMPTY = ByteBuffer.allocate(0);
+
+
+ SSLEngine sslEngine;
+ // Last result
+ SSLEngineResult unwrapR;
+
+ boolean handshakeDone = false;
+ boolean handshakeInProgress = false;
+ Object handshakeSync = new Object();
+ boolean flushing = false;
+
+ IOBuffer in = new IOBuffer(this);
+ IOBuffer out = new IOBuffer(this);
+
+ long handshakeTimeout = 20000;
+ // Used for session reuse
+ String host;
+ int port;
+
+ ByteBuffer myAppOutData;
+ ByteBuffer myNetOutData;
+ private static boolean debugWrap = false;
+
+ /*
+ * Special: SSL works in packet mode, and we may receive an incomplete
+ * packet. This should be in compacted write mode (i.e. data from 0 to pos,
+ * limit at end )
+ */
+ ByteBuffer myNetInData;
+ ByteBuffer myAppInData;
+ boolean client = true;
+
+ private SSLContext sslCtx;
+
+ private boolean closeHandshake = false;
+
+ public SslChannel() {
+ }
+
+ /**
+ * Setting the host/port enables clients to reuse SSL session -
+ * less traffic and encryption overhead at startup, assuming the
+ * server caches the session ( i.e. single server or distributed cache ).
+ *
+ * SSL ticket extension is another possibility.
+ */
+ public SslChannel setTarget(String host, int port) {
+ this.host = host;
+ this.port = port;
+ return this;
+ }
+
+ private synchronized void initSsl() throws GeneralSecurityException {
+ if (sslEngine != null) {
+ log.severe("Double initSsl");
+ return;
+ }
+
+ if (client) {
+ if (port > 0) {
+ sslEngine = sslCtx.createSSLEngine(host, port);
+ } else {
+ sslEngine = sslCtx.createSSLEngine();
+ }
+ sslEngine.setUseClientMode(client);
+ } else {
+ sslEngine = sslCtx.createSSLEngine();
+ sslEngine.setUseClientMode(false);
+
+ }
+
+ // Some VMs have broken ciphers.
+ if (JsseSslProvider.enabledCiphers != null) {
+ sslEngine.setEnabledCipherSuites(JsseSslProvider.enabledCiphers);
+ }
+
+ SSLSession session = sslEngine.getSession();
+
+ int packetBuffer = session.getPacketBufferSize();
+ myAppOutData = ByteBuffer.allocate(session.getApplicationBufferSize());
+ myNetOutData = ByteBuffer.allocate(packetBuffer);
+ myAppInData = ByteBuffer.allocate(session.getApplicationBufferSize());
+ myNetInData = ByteBuffer.allocate(packetBuffer);
+ myNetInData.flip();
+ myNetOutData.flip();
+ myAppInData.flip();
+ myAppOutData.flip();
+ }
+
+ public SslChannel withServer() {
+ client = false;
+ return this;
+ }
+
+
+ @Override
+ public synchronized void setSink(IOChannel net) throws IOException {
+ try {
+ if (sslEngine == null) {
+ initSsl();
+ }
+ super.setSink(net);
+ } catch (GeneralSecurityException e) {
+ log.log(Level.SEVERE, "Error initializing ", e);
+ }
+ }
+
+ @Override
+ public IOBuffer getIn() {
+ return in;
+ }
+
+ @Override
+ public IOBuffer getOut() {
+ return out;
+ }
+
+ /**
+ * Typically called when a dataReceived callback is passed up.
+ * It's up to the higher layer to decide if it can handle more data
+ * and disable read interest and manage its buffers.
+ *
+ * We have to use one buffer.
+ * @throws IOException
+ */
+ public int processInput(IOBuffer netIn, IOBuffer appIn) throws IOException {
+ if (log.isLoggable(Level.FINEST)) {
+ log.info("JSSE: processInput " + handshakeInProgress + " " + netIn.getBufferCount());
+ }
+ synchronized(handshakeSync) {
+ if (!handshakeDone && !handshakeInProgress) {
+ handshakeInProgress = true;
+ handleHandshking();
+ return 0;
+ }
+ if (handshakeInProgress) {
+ return 0; // leave it there
+ }
+ }
+ return processRealInput(netIn, appIn);
+ }
+
+ private synchronized int processRealInput(IOBuffer netIn, IOBuffer appIn) throws IOException {
+ int rd = 0;
+ boolean needsMore = true;
+ boolean notEnough = false;
+
+ while (needsMore) {
+ if (netIn.isClosedAndEmpty()) {
+ appIn.close();
+ sendHandleReceivedCallback();
+ return -1;
+ }
+ myNetInData.compact();
+ int rdNow;
+ try {
+ rdNow = netIn.read(myNetInData);
+ } finally {
+ myNetInData.flip();
+ }
+ if (rdNow == 0 && (myNetInData.remaining() == 0 ||
+ notEnough)) {
+ return rd;
+ }
+ if (rdNow == -1) {
+ appIn.close();
+ sendHandleReceivedCallback();
+ return rd;
+ }
+
+ notEnough = true; // next read of 0
+ while (myNetInData.remaining() > 0) {
+ myAppInData.compact();
+ try {
+ unwrapR = sslEngine.unwrap(myNetInData, myAppInData);
+ } catch (SSLException ex) {
+ log.warning("Read error: " + ex);
+ close();
+ return -1;
+ } finally {
+ myAppInData.flip();
+ }
+ if (myAppInData.remaining() > 0) {
+ in.write(myAppInData); // all will be written
+ }
+ if (unwrapR.getStatus() == Status.CLOSED) {
+ in.close();
+ if (unwrapR.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
+ // TODO: send/receive one more packet ( handshake mode ? )
+ synchronized(handshakeSync) {
+ handshakeInProgress = true;
+ closeHandshake = true;
+ }
+ handleHandshking();
+
+ startSending();
+ }
+ break;
+ }
+
+ if (unwrapR.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
+ tasks();
+ }
+ if (unwrapR.getStatus() == Status.BUFFER_OVERFLOW ||
+ unwrapR.getStatus() == Status.BUFFER_UNDERFLOW) {
+ log.severe("Unhandled overflow");
+ break;
+ }
+ }
+ sendHandleReceivedCallback();
+
+
+ }
+ return rd;
+ }
+
+ protected SSLEngineResult.HandshakeStatus tasks() {
+ Runnable r = null;
+ while ( (r = sslEngine.getDelegatedTask()) != null) {
+ r.run();
+ }
+ return sslEngine.getHandshakeStatus();
+ }
+
+ public void startSending() throws IOException {
+
+ flushing = true;
+ boolean needHandshake = false;
+ synchronized(handshakeSync) {
+ if (handshakeInProgress) {
+ return; // don't bother me.
+ }
+ if (!handshakeDone) {
+ handshakeInProgress = true;
+ needHandshake = true;
+ }
+ }
+ if (needHandshake) {
+ handleHandshking();
+ return; // can't write yet.
+ }
+
+ startRealSending();
+ }
+
+ public void close() throws IOException {
+ if (net.getOut().isAppendClosed()) {
+ return;
+ }
+ sslEngine.closeOutbound(); // mark as closed
+ synchronized(myNetOutData) {
+ myNetOutData.compact();
+
+ SSLEngineResult wrap;
+ try {
+ wrap = sslEngine.wrap(EMPTY, myNetOutData);
+ if (wrap.getStatus() != Status.CLOSED) {
+ log.warning("Unexpected close status " + wrap);
+ }
+ } catch (Throwable t ) {
+ log.info("Error wrapping " + myNetOutData);
+ } finally {
+ myNetOutData.flip();
+ }
+ if (myNetOutData.remaining() > 0) {
+ net.getOut().write(myNetOutData);
+ }
+ }
+ // TODO: timer to close socket if we don't get
+ // clean close handshake
+ super.close();
+ }
+
+ private Object sendLock = new Object();
+
+ private JsseSslProvider sslProvider;
+
+ private void startRealSending() throws IOException {
+ // Only one thread at a time
+ synchronized (sendLock) {
+ while (true) {
+
+ myAppOutData.compact();
+ int rd;
+ try {
+ rd = out.read(myAppOutData);
+ } finally {
+ myAppOutData.flip();
+ }
+ if (rd == 0) {
+ break;
+ }
+ if (rd < 0) {
+ close();
+ break;
+ }
+
+ SSLEngineResult wrap;
+ synchronized(myNetOutData) {
+ myNetOutData.compact();
+ try {
+ wrap = sslEngine.wrap(myAppOutData,
+ myNetOutData);
+ } finally {
+ myNetOutData.flip();
+ }
+ net.getOut().write(myNetOutData);
+ }
+ if (wrap != null) {
+ switch (wrap.getStatus()) {
+ case BUFFER_UNDERFLOW: {
+ break;
+ }
+ case OK: {
+ break;
+ }
+ case BUFFER_OVERFLOW: {
+ throw new IOException("Overflow");
+ }
+ }
+ }
+ }
+ }
+
+ net.startSending();
+ }
+
+
+
+ // SSL handshake require slow tasks - that will need to be executed in a
+ // thread anyways. Better to keep it simple ( the code is very complex ) -
+ // and do the initial handshake in a thread, not in the IO thread.
+ // We'll need to unregister and register again from the selector.
+ private void handleHandshking() {
+ if (log.isLoggable(Level.FINEST)) {
+ log.info("Starting handshake");
+ }
+ synchronized(handshakeSync) {
+ handshakeInProgress = true;
+ }
+
+ sslProvider.handshakeExecutor.execute(this);
+ }
+
+ private void endHandshake() throws IOException {
+ if (log.isLoggable(Level.FINEST)) {
+ log.info("Handshake done " + net.getIn().available());
+ }
+ synchronized(handshakeSync) {
+ handshakeDone = true;
+ handshakeInProgress = false;
+ }
+ if (flushing) {
+ flushing = false;
+ startSending();
+ }
+ if (myNetInData.remaining() > 0 || net.getIn().available() > 0) {
+ // Last SSL packet also includes data.
+ handleReceived(net);
+ }
+ }
+
+ /**
+ * Actual handshake magic, in background thread.
+ */
+ public void run() {
+ try {
+ boolean initial = true;
+ SSLEngineResult wrap = null;
+
+ HandshakeStatus hstatus = sslEngine.getHandshakeStatus();
+ if (!closeHandshake &&
+ (hstatus == HandshakeStatus.NOT_HANDSHAKING || initial)) {
+ sslEngine.beginHandshake();
+ hstatus = sslEngine.getHandshakeStatus();
+ }
+
+ long t0 = System.currentTimeMillis();
+
+ while (hstatus != HandshakeStatus.NOT_HANDSHAKING
+ && hstatus != HandshakeStatus.FINISHED
+ && !net.getIn().isAppendClosed()) {
+ if (System.currentTimeMillis() - t0 > handshakeTimeout) {
+ throw new TimeoutException();
+ }
+ if (wrap != null && wrap.getStatus() == Status.CLOSED) {
+ break;
+ }
+ if (log.isLoggable(Level.FINEST)) {
+ log.info("-->doHandshake() loop: status = " + hstatus + " " +
+ sslEngine.getHandshakeStatus());
+ }
+
+ if (hstatus == HandshakeStatus.NEED_WRAP) {
+ // || initial - for client
+ initial = false;
+ synchronized(myNetOutData) {
+ while (hstatus == HandshakeStatus.NEED_WRAP) {
+ myNetOutData.compact();
+ try {
+ wrap = sslEngine.wrap(myAppOutData, myNetOutData);
+ } catch (Throwable t) {
+ log.log(Level.SEVERE, "Wrap error", t);
+ close();
+ return;
+ } finally {
+ myNetOutData.flip();
+ }
+ if (myNetOutData.remaining() > 0) {
+ net.getOut().write(myNetOutData);
+ }
+ hstatus = wrap.getHandshakeStatus();
+ }
+ }
+ net.startSending();
+ } else if (hstatus == HandshakeStatus.NEED_UNWRAP) {
+ while (hstatus == HandshakeStatus.NEED_UNWRAP) {
+ // If we have few remaining bytes - process them
+ if (myNetInData.remaining() > 0) {
+ myAppInData.clear();
+ if (debugWrap) {
+ log.info("UNWRAP: rem=" + myNetInData.remaining());
+ }
+ wrap = sslEngine.unwrap(myNetInData, myAppInData);
+ hstatus = wrap.getHandshakeStatus();
+ myAppInData.flip();
+ if (myAppInData.remaining() > 0) {
+ log.severe("Unexpected data after unwrap");
+ }
+ if (wrap.getStatus() == Status.CLOSED) {
+ break;
+ }
+ }
+ // Still need unwrap
+ if (wrap == null
+ || wrap.getStatus() == Status.BUFFER_UNDERFLOW
+ || (hstatus == HandshakeStatus.NEED_UNWRAP && myNetInData.remaining() == 0)) {
+ myNetInData.compact();
+ // non-blocking
+ int rd;
+ try {
+ rd = net.getIn().read(myNetInData);
+ if (debugWrap) {
+ log.info("Read: " + rd);
+ }
+ } finally {
+ myNetInData.flip();
+ }
+ if (rd == 0) {
+ if (debugWrap) {
+ log.info("Wait: " + handshakeTimeout);
+ }
+ net.getIn().waitData(handshakeTimeout);
+ rd = net.getIn().read(myNetInData);
+ if (debugWrap) {
+ log.info("Read after wait: " + rd);
+ }
+ }
+ if (rd < 0) {
+ // in closed
+ break;
+ }
+ }
+ if (log.isLoggable(Level.FINEST)) {
+ log.info("Unwrap chunk done " + hstatus + " " + wrap
+ + " " + sslEngine.getHandshakeStatus());
+ }
+
+ }
+
+ // rd may have some input bytes.
+ } else if (hstatus == HandshakeStatus.NEED_TASK) {
+ long t0task = System.currentTimeMillis();
+ Runnable r;
+ while ((r = sslEngine.getDelegatedTask()) != null) {
+ r.run();
+ }
+ long t1task = System.currentTimeMillis();
+ hstatus = sslEngine.getHandshakeStatus();
+ if (log.isLoggable(Level.FINEST)) {
+ log.info("Tasks done in " + (t1task - t0task) + " new status " +
+ hstatus);
+ }
+
+ }
+ if (hstatus == HandshakeStatus.NOT_HANDSHAKING) {
+ //log.warning("NOT HANDSHAKING " + this);
+ break;
+ }
+ }
+ endHandshake();
+ processRealInput(net.getIn(), in);
+ } catch (Throwable t) {
+ log.log(Level.SEVERE, "Error handshaking", t);
+ try {
+ close();
+ net.close();
+ sendHandleReceivedCallback();
+ } catch (IOException ex) {
+ log.log(Level.SEVERE, "Error closing", ex);
+ }
+ }
+ }
+
+
+ @Override
+ public void handleReceived(IOChannel ch) throws IOException {
+ processInput(net.getIn(), in);
+ // Maybe we don't have data - that's fine.
+ sendHandleReceivedCallback();
+ }
+
+ SslChannel setSslContext(SSLContext sslCtx) {
+ this.sslCtx = sslCtx;
+ return this;
+ }
+
+ SslChannel setSslProvider(JsseSslProvider con) {
+ this.sslProvider = con;
+ return this;
+ }
+
+ public Object getAttribute(String name) {
+ if (SslProvider.ATT_SSL_CERT.equals(name)) {
+ try {
+ return sslEngine.getSession().getPeerCertificateChain();
+ } catch (SSLPeerUnverifiedException e) {
+ return null; // no re-negotiation
+ }
+ } else if (SslProvider.ATT_SSL_CIPHER.equals(name)) {
+ return sslEngine.getSession().getCipherSuite();
+ } else if (SslProvider.ATT_SSL_KEY_SIZE.equals(name)) {
+ // looks like we need to get it from the string cipher
+ CipherData c_aux[] = ciphers;
+
+ int size = 0;
+ String cipherSuite = sslEngine.getSession().getCipherSuite();
+ for (int i = 0; i < c_aux.length; i++) {
+ if (cipherSuite.indexOf(c_aux[i].phrase) >= 0) {
+ size = c_aux[i].keySize;
+ break;
+ }
+ }
+ return size;
+ } else if (SslProvider.ATT_SSL_SESSION_ID.equals(name)) {
+ byte [] ssl_session = sslEngine.getSession().getId();
+ if ( ssl_session == null)
+ return null;
+ StringBuilder buf=new StringBuilder();
+ for(int x=0; x<ssl_session.length; x++) {
+ String digit=Integer.toHexString(ssl_session[x]);
+ if (digit.length()<2) buf.append('0');
+ if (digit.length()>2) digit=digit.substring(digit.length()-2);
+ buf.append(digit);
+ }
+ return buf.toString();
+ }
+
+ if (net != null) {
+ return net.getAttribute(name);
+ }
+ return null;
+ }
+
+
+ /**
+ * Simple data class that represents the cipher being used, along with the
+ * corresponding effective key size. The specified phrase must appear in the
+ * name of the cipher suite to be recognized.
+ */
+
+ static final class CipherData {
+
+ public String phrase = null;
+
+ public int keySize = 0;
+
+ public CipherData(String phrase, int keySize) {
+ this.phrase = phrase;
+ this.keySize = keySize;
+ }
+
+ }
+
+
+ /**
+ * A mapping table to determine the number of effective bits in the key
+ * when using a cipher suite containing the specified cipher name. The
+ * underlying data came from the TLS Specification (RFC 2246), Appendix C.
+ */
+ static final CipherData ciphers[] = {
+ new CipherData("_WITH_NULL_", 0),
+ new CipherData("_WITH_IDEA_CBC_", 128),
+ new CipherData("_WITH_RC2_CBC_40_", 40),
+ new CipherData("_WITH_RC4_40_", 40),
+ new CipherData("_WITH_RC4_128_", 128),
+ new CipherData("_WITH_DES40_CBC_", 40),
+ new CipherData("_WITH_DES_CBC_", 56),
+ new CipherData("_WITH_3DES_EDE_CBC_", 168),
+ new CipherData("_WITH_AES_128_CBC_", 128),
+ new CipherData("_WITH_AES_256_CBC_", 256)
+ };
+
+}
Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/SslChannel.java
------------------------------------------------------------------------------
svn:eol-style = native
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org