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