From: costin Date: Tue, 25 May 2010 06:42:26 +0000 (+0000) Subject: Moved the JSSE code to separate package. Added a bunch of workarounds to support... X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=f7aa397425d4b3b2782c7461ad5a60b9e5fea917;p=tomcat7.0 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. git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@947935 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java deleted file mode 100644 index e2ae08a29..000000000 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java +++ /dev/null @@ -1,518 +0,0 @@ -/* - */ -package org.apache.tomcat.lite.io; - -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.SSLSession; -import javax.net.ssl.SSLEngineResult.HandshakeStatus; -import javax.net.ssl.SSLEngineResult.Status; - - -public class SslChannel extends IOChannel implements Runnable { - - static Logger log = Logger.getLogger("SSL"); - - SSLEngine sslEngine; - // Last result - SSLEngineResult unwrapR; - - boolean handshakeDone = false; - boolean handshakeInProgress = false; - boolean flushing = false; - - IOBuffer in = new IOBuffer(this); - IOBuffer out = new IOBuffer(this); - - long handshakeTimeout = 10000; - // Used for session reuse - String host; - int port; - - public SslChannel() { - } - - ByteBuffer myAppOutData; - ByteBuffer myNetOutData; - - /* - * 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; - - /** - * 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) { - 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); - String[] cs = sslEngine.getEnabledCipherSuites(); - cs =sslEngine.getSupportedCipherSuites(); - - } - SSLSession session = sslEngine.getSession(); - - myAppOutData = ByteBuffer.allocate(session.getApplicationBufferSize()); - myNetOutData = ByteBuffer.allocate(session.getPacketBufferSize()); - myAppInData = ByteBuffer.allocate(session.getApplicationBufferSize()); - myNetInData = ByteBuffer.allocate(session.getPacketBufferSize()); - myNetInData.flip(); - myNetOutData.flip(); - myAppInData.flip(); - myAppOutData.flip(); - - // TODO: enable anon suites - //sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites()); - } - - public SslChannel withServer() { - client = false; - return this; - } - - - @Override - public synchronized void setSink(IOChannel net) throws IOException { - try { - 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()); - } - 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 ? ) - 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(); - } - - static ByteBuffer EMPTY = ByteBuffer.allocate(0); - - public void startSending() throws IOException { - - flushing = true; - - if (handshakeInProgress) { - return; // don't bother me. - } - - if (!handshakeDone) { - handshakeInProgress = true; - 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 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"); - } - handshakeInProgress = true; - - ((SslConnector) connector).handshakeExecutor.execute(this); - } - - private void endHandshake() throws IOException { - if (log.isLoggable(Level.FINEST)) { - log.info("Handshake done " + net.getIn().available()); - } - 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) { - myNetOutData.compact(); - - try { - wrap = sslEngine.wrap(myAppOutData, myNetOutData); - } catch (Throwable t) { - t.printStackTrace(); - close(); - return; - } finally { - myNetOutData.flip(); - } - - hstatus = wrap.getHandshakeStatus(); - if (myNetOutData.remaining() > 0) { - net.getOut().write(myNetOutData); - } - } - 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(); - wrap = sslEngine.unwrap(myNetInData, myAppInData); - hstatus = wrap.getHandshakeStatus(); - - myAppInData.flip(); - if (myAppInData.remaining() > 0) { - throw new IOException("Unexpected data"); - } - 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); - } finally { - myNetInData.flip(); - } - if (rd == 0) { - net.getIn().waitData(handshakeTimeout - - (System.currentTimeMillis() - t0)); - rd = net.getIn().read(myNetInData); - } - 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(); - } - - public SslChannel setSslContext(SSLContext sslCtx) { - this.sslCtx = sslCtx; - return this; - } - - public SslChannel setSslConnector(SslConnector con) { - this.connector = con; - return this; - } -} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslConnector.java deleted file mode 100644 index cdb4acef9..000000000 --- a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslConnector.java +++ /dev/null @@ -1,397 +0,0 @@ -/* - */ -package org.apache.tomcat.lite.io; - -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.GeneralSecurityException; -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.InvalidKeySpecException; -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.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedKeyManager; -import javax.net.ssl.X509TrustManager; - - -public class SslConnector extends IOConnector { - - /** - * 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 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 SslConnector() { - } - - public void start() { - - } - - public 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; - } - - public SslChannel channel(String host, int port) { - return new SslChannel() - .setTarget(host, port) - .setSslContext(getSSLContext()) - .setSslConnector(this); - } - - public SslChannel serverChannel() { - return new SslChannel() - .setSslContext(getSSLContext()) - .setSslConnector(this).withServer(); - } - - @Override - 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.addFilterAfter(dch); - first = dch; - } - - IOChannel sslch = serverChannel(); - sslch.setSink(first); - first.addFilterAfter(sslch); - - if (debug) { - DumpChannel dch2 = new DumpChannel("S-CLR-" + id); - sslch.addFilterAfter(dch2); - sslch = dch2; - id++; - } - - sc.handleConnected(sslch); - } - }, port, extra); - } - - @Override - 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.addFilterAfter(dch); - first = dch; - } - - IOChannel sslch = channel(host, port); - sslch.setSink(first); - first.addFilterAfter(sslch); - - if (debug) { - DumpChannel dch2 = new DumpChannel("CLR-" + id); - sslch.addFilterAfter(dch2); - sslch = dch2; - id++; - } - - sc.handleConnected(sslch); - } - - }); - } - - public SslConnector withKeyManager(KeyManager[] kms) { - this.keyManager = kms; - return this; - } - - public SslConnector setKeysFile(String file, String pass) throws IOException { - return setKeys(new FileInputStream(file), pass); - } - - public SslConnector setKeysResource(String res, String pass) throws IOException { - return setKeys(this.getClass().getClassLoader().getResourceAsStream(res), - pass); - } - - public SslConnector setKeys(InputStream file, String pass) { - char[] passphrase = pass.toCharArray(); - KeyStore ks; - try { - ks = KeyStore.getInstance("JKS"); - 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) { - // 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 SslConnector setKeys(X509Certificate cert, PrivateKey privKey) { - keyManager = new KeyManager[] { - new TestKeyManager(cert, privKey) - }; - return this; - } - - /** - * Initialize using a PEM certificate and key bytes. - * ( TODO: base64 dep to set the key as PEM ) - * - * - * Key was generated with - * keytool -genkey -alias server -keyalg RSA -storepass changeit - * keytool -selfcert -storepass changeit -alias server - * - * Then the bytes printed with printPrivateKey() - * - * I found no way to generate the self-signed keys from jsse - * except CLI. - * - */ - public SslConnector setKeys(String certPem, byte[] keyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException, GeneralSecurityException { - // convert key - KeyFactory kf = KeyFactory.getInstance("RSA"); - PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec(keyBytes); - PrivateKey priv = kf.generatePrivate (keysp); - - // Convert cert pem to certificate - InputStream is = new ByteArrayInputStream(certPem.getBytes()); - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - final X509Certificate cert = (X509Certificate) cf.generateCertificate(is); - - setKeys(cert, priv); - - 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; - } - } - - public static void fixUrlConnection() { - try { - SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, SslConnector.trustAllCerts, null); - javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory( - sc.getSocketFactory()); - } 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 diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslProvider.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslProvider.java new file mode 100644 index 000000000..94e86ae4a --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslProvider.java @@ -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; + +} diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/JsseSslProvider.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/JsseSslProvider.java new file mode 100644 index 000000000..7d72db5c8 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/JsseSslProvider.java @@ -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 diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/SslChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/SslChannel.java new file mode 100644 index 000000000..bd9301a65 --- /dev/null +++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/SslChannel.java @@ -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; x2) 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) + }; + +}