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-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org