summaryrefslogtreecommitdiffstats
path: root/libjava/classpath/gnu/javax/net/ssl/provider/SSLSocket.java
diff options
context:
space:
mode:
authormark <mark@138bc75d-0d04-0410-961f-82ee72b054a4>2006-03-10 21:46:48 +0000
committermark <mark@138bc75d-0d04-0410-961f-82ee72b054a4>2006-03-10 21:46:48 +0000
commitce57ab760f69de6db452def7ffbf5b114a2d8694 (patch)
treeea38c56431c5d4528fb54254c3f8e50f517bede3 /libjava/classpath/gnu/javax/net/ssl/provider/SSLSocket.java
parent50996fe55769882de3f410896032c887f0ff0d04 (diff)
downloadppe42-gcc-ce57ab760f69de6db452def7ffbf5b114a2d8694.tar.gz
ppe42-gcc-ce57ab760f69de6db452def7ffbf5b114a2d8694.zip
Imported GNU Classpath 0.90
* scripts/makemake.tcl: Set gnu/java/awt/peer/swing to ignore. * gnu/classpath/jdwp/VMFrame.java (SIZE): New constant. * java/lang/VMCompiler.java: Use gnu.java.security.hash.MD5. * java/lang/Math.java: New override file. * java/lang/Character.java: Merged from Classpath. (start, end): Now 'int's. (canonicalName): New field. (CANONICAL_NAME, NO_SPACES_NAME, CONSTANT_NAME): New constants. (UnicodeBlock): Added argument. (of): New overload. (forName): New method. Updated unicode blocks. (sets): Updated. * sources.am: Regenerated. * Makefile.in: Likewise. git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@111942 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'libjava/classpath/gnu/javax/net/ssl/provider/SSLSocket.java')
-rw-r--r--libjava/classpath/gnu/javax/net/ssl/provider/SSLSocket.java3530
1 files changed, 3530 insertions, 0 deletions
diff --git a/libjava/classpath/gnu/javax/net/ssl/provider/SSLSocket.java b/libjava/classpath/gnu/javax/net/ssl/provider/SSLSocket.java
new file mode 100644
index 00000000000..a564659c02c
--- /dev/null
+++ b/libjava/classpath/gnu/javax/net/ssl/provider/SSLSocket.java
@@ -0,0 +1,3530 @@
+/* SSLSocket.java -- the SSL socket class.
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is a part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at
+your option) any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
+USA
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.javax.net.ssl.provider;
+
+import java.io.BufferedOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+import java.math.BigInteger;
+
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+
+import java.nio.channels.SocketChannel;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import javax.net.ssl.HandshakeCompletedEvent;
+import javax.net.ssl.HandshakeCompletedListener;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLProtocolException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.ConfirmationCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextInputCallback;
+
+import gnu.classpath.debug.Component;
+import gnu.classpath.debug.SystemLogger;
+
+import gnu.java.security.Registry;
+import gnu.javax.security.auth.callback.DefaultCallbackHandler;
+import gnu.java.security.hash.HashFactory;
+import gnu.java.security.hash.IMessageDigest;
+import gnu.javax.crypto.key.IKeyAgreementParty;
+import gnu.javax.crypto.key.KeyAgreementFactory;
+import gnu.javax.crypto.key.KeyAgreementException;
+import gnu.javax.crypto.key.OutgoingMessage;
+import gnu.javax.crypto.key.IncomingMessage;
+import gnu.javax.crypto.key.dh.DiffieHellmanKeyAgreement;
+import gnu.javax.crypto.key.dh.ElGamalKeyAgreement;
+import gnu.javax.crypto.key.dh.GnuDHPrivateKey;
+import gnu.javax.crypto.key.dh.GnuDHPublicKey;
+import gnu.javax.crypto.key.srp6.SRPPrivateKey;
+import gnu.javax.crypto.key.srp6.SRPPublicKey;
+import gnu.javax.crypto.key.srp6.SRP6KeyAgreement;
+import gnu.javax.crypto.mac.IMac;
+import gnu.javax.crypto.mode.IMode;
+import gnu.javax.crypto.prng.ARCFour;
+import gnu.java.security.prng.IRandom;
+import gnu.java.security.prng.LimitReachedException;
+import gnu.javax.crypto.sasl.srp.SRPAuthInfoProvider;
+import gnu.javax.crypto.sasl.srp.SRPRegistry;
+import gnu.java.security.sig.ISignature;
+import gnu.java.security.sig.SignatureFactory;
+import gnu.java.security.sig.dss.DSSSignature;
+import gnu.java.security.sig.rsa.EME_PKCS1_V1_5;
+import gnu.java.security.sig.rsa.RSA;
+
+import gnu.javax.net.ssl.SRPTrustManager;
+
+/**
+ * This is the core of the Jessie SSL implementation; it implements the {@link
+ * javax.net.ssl.SSLSocket} for normal and "wrapped" sockets, and handles all
+ * protocols implemented by this library.
+ */
+final class SSLSocket extends javax.net.ssl.SSLSocket
+{
+
+ // This class is almost unbearably large and complex, but is laid out
+ // as follows:
+ //
+ // 1. Fields.
+ // 2. Constructors.
+ // 3. SSLSocket methods. These are the public methods defined in
+ // javax.net.ssl.SSLSocket.
+ // 4. Socket methods. These override the public methods of java.net.Socket,
+ // and delegate the method call to either the underlying socket if this is
+ // a wrapped socket, or to the superclass.
+ // 5. Package-private methods that various pieces of Jessie use.
+ // 6. Private methods. These compose the SSL handshake.
+ //
+ // Each part is preceeded by a form feed.
+
+ // Constants and fields.
+ // -------------------------------------------------------------------------
+
+ // Debuggery.
+ private static final boolean DEBUG_HANDSHAKE_LAYER = true;
+ private static final boolean DEBUG_KEY_EXCHANGE = false;
+ private static final Logger logger = SystemLogger.SYSTEM;
+
+ // Fields for using this class as a wrapped socket.
+ private Socket underlyingSocket;
+ private int underlyingPort;
+ private boolean autoClose;
+
+ // Cryptography fields.
+ SessionContext sessionContext;
+ Session session;
+ LinkedList handshakeListeners;
+ private boolean clientMode, wantClientAuth, needClientAuth, createSessions;
+ private boolean handshakeDone;
+
+ // I/O fields.
+ private String remoteHost;
+ private InputStream socketIn;
+ private OutputStream socketOut;
+ private InputStream applicationIn;
+ private OutputStream applicationOut;
+ private InputStream handshakeIn;
+ private OutputStream handshakeOut;
+// private ThreadGroup recordLayer;
+ RecordInput recordInput;
+// RecordOutput recordOutput;
+ private long handshakeTime;
+
+ private SocketChannel channel;
+
+ static SortedSet supportedProtocols = new TreeSet();
+ static List supportedSuites = new ArrayList(30);
+
+ // Static initializer.
+ // -------------------------------------------------------------------------
+
+ static
+ {
+ //supportedProtocols.add(ProtocolVersion.TLS_1_1);
+ supportedProtocols.add(ProtocolVersion.TLS_1);
+ supportedProtocols.add(ProtocolVersion.SSL_3);
+
+ // These are in preference order. It's my preference order, but I'm not
+ // a total idiot.
+ supportedSuites.add(CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_RSA_WITH_RC4_128_MD5);
+ supportedSuites.add(CipherSuite.TLS_RSA_WITH_RC4_128_SHA);
+ supportedSuites.add(CipherSuite.TLS_DHE_DSS_WITH_DES_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DHE_RSA_WITH_DES_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DH_DSS_WITH_DES_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DH_RSA_WITH_DES_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_RSA_WITH_DES_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_RSA_EXPORT_WITH_DES40_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_RSA_EXPORT_WITH_RC4_40_MD5);
+ supportedSuites.add(CipherSuite.TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA);
+ supportedSuites.add(CipherSuite.TLS_RSA_WITH_NULL_MD5);
+ supportedSuites.add(CipherSuite.TLS_RSA_WITH_NULL_SHA);
+ }
+
+ // Constructors.
+ // -------------------------------------------------------------------------
+
+ SSLSocket(Socket socket, String host, int port, boolean autoClose)
+ throws IOException
+ {
+ underlyingSocket = socket;
+ remoteHost = host;
+ underlyingPort = port;
+ this.autoClose = autoClose;
+ initialize();
+ }
+
+ SSLSocket (Socket socket, SocketChannel channel) throws IOException
+ {
+ underlyingSocket = socket;
+ this.channel = channel;
+ initialize ();
+ }
+
+ SSLSocket() throws IOException
+ {
+ super();
+ initialize();
+ }
+
+ SSLSocket(InetAddress addr, int port) throws IOException
+ {
+ super(addr, port);
+ initialize();
+ remoteHost = addr.getHostName();
+ if (remoteHost == null)
+ {
+ remoteHost = addr.getHostAddress();
+ }
+ }
+
+ SSLSocket(InetAddress addr, int port, InetAddress laddr, int lport)
+ throws IOException
+ {
+ super(addr, port, laddr, lport);
+ initialize();
+ remoteHost = addr.getHostName();
+ if (remoteHost == null)
+ remoteHost = addr.getHostAddress();
+ }
+
+ SSLSocket(String host, int port) throws IOException
+ {
+ super(host, port);
+ initialize();
+ remoteHost = host;
+ }
+
+ SSLSocket(String host, int port, InetAddress laddr, int lport)
+ throws IOException
+ {
+ super(host, port, laddr, lport);
+ initialize();
+ remoteHost = host;
+ }
+
+ private void initialize()
+ {
+ session = new Session();
+ session.enabledSuites = new ArrayList(supportedSuites);
+ session.enabledProtocols = new TreeSet(supportedProtocols);
+ session.protocol = ProtocolVersion.TLS_1;
+ session.params.setVersion (ProtocolVersion.TLS_1);
+ handshakeListeners = new LinkedList();
+ handshakeDone = false;
+ }
+
+ // SSL methods.
+ // -------------------------------------------------------------------------
+
+ public void addHandshakeCompletedListener(HandshakeCompletedListener l)
+ {
+ synchronized (handshakeListeners)
+ {
+ if (l == null)
+ throw new NullPointerException();
+ if (!handshakeListeners.contains(l))
+ handshakeListeners.add(l);
+ }
+ }
+
+ public void removeHandshakeCompletedListener(HandshakeCompletedListener l)
+ {
+ synchronized (handshakeListeners)
+ {
+ handshakeListeners.remove(l);
+ }
+ }
+
+ public String[] getEnabledProtocols()
+ {
+ synchronized (session.enabledProtocols)
+ {
+ try
+ {
+ return (String[]) Util.transform(session.enabledProtocols.toArray(),
+ String.class, "toString", null);
+ }
+ catch (Exception x)
+ {
+ RuntimeException re = new RuntimeException (x.getMessage());
+ re.initCause (x);
+ throw re;
+ }
+ }
+ }
+
+ public void setEnabledProtocols(String[] protocols)
+ {
+ if (protocols == null || protocols.length == 0)
+ throw new IllegalArgumentException();
+ for (int i = 0; i < protocols.length; i++)
+ {
+ if (!(protocols[i].equalsIgnoreCase("SSLv3") ||
+ protocols[i].equalsIgnoreCase("TLSv1") ||
+ protocols[i].equalsIgnoreCase("TLSv1.1")))
+ {
+ throw new
+ IllegalArgumentException("unsupported protocol: " +
+ protocols[i]);
+ }
+ }
+ synchronized (session.enabledProtocols)
+ {
+ session.enabledProtocols.clear();
+ for (int i = 0; i < protocols.length; i++)
+ {
+ if (protocols[i].equalsIgnoreCase("SSLv3"))
+ {
+ session.enabledProtocols.add(ProtocolVersion.SSL_3);
+ }
+ else if (protocols[i].equalsIgnoreCase("TLSv1"))
+ {
+ session.enabledProtocols.add(ProtocolVersion.TLS_1);
+ }
+ else
+ {
+ session.enabledProtocols.add(ProtocolVersion.TLS_1_1);
+ }
+ }
+ }
+ }
+
+ public String[] getSupportedProtocols()
+ {
+ return new String[] { /* "TLSv1.1", */ "TLSv1", "SSLv3" };
+ }
+
+ public String[] getEnabledCipherSuites()
+ {
+ synchronized (session.enabledSuites)
+ {
+ try
+ {
+ return (String[]) Util.transform(session.enabledSuites.toArray(),
+ String.class, "toString", null);
+ }
+ catch (Exception x)
+ {
+ RuntimeException re = new RuntimeException (x.getMessage());
+ re.initCause (x);
+ throw re;
+ }
+ }
+ }
+
+ public void setEnabledCipherSuites(String[] suites)
+ {
+ if (suites == null || suites.length == 0)
+ throw new IllegalArgumentException();
+ for (int i = 0; i < suites.length; i++)
+ if (CipherSuite.forName(suites[i]) == null)
+ throw new IllegalArgumentException("unsupported suite: " +
+ suites[i]);
+ synchronized (session.enabledSuites)
+ {
+ session.enabledSuites.clear();
+ for (int i = 0; i < suites.length; i++)
+ {
+ CipherSuite suite = CipherSuite.forName(suites[i]);
+ if (!session.enabledSuites.contains(suite))
+ {
+ session.enabledSuites.add(suite);
+ }
+ }
+ }
+ }
+
+ public String[] getSupportedCipherSuites()
+ {
+ return (String[]) CipherSuite.availableSuiteNames().toArray(new String[52]);
+ }
+
+ public SSLSession getSession()
+ {
+ return session;
+ }
+
+ public boolean getEnableSessionCreation()
+ {
+ return createSessions;
+ }
+
+ public void setEnableSessionCreation(boolean flag)
+ {
+ createSessions = flag;
+ }
+
+ public boolean getNeedClientAuth()
+ {
+ return needClientAuth;
+ }
+
+ public void setNeedClientAuth(boolean flag)
+ {
+ needClientAuth = flag;
+ }
+
+ public boolean getWantClientAuth()
+ {
+ return wantClientAuth;
+ }
+
+ public void setWantClientAuth(boolean flag)
+ {
+ wantClientAuth = flag;
+ }
+
+ public boolean getUseClientMode()
+ {
+ return clientMode;
+ }
+
+ public void setUseClientMode(boolean flag)
+ {
+ this.clientMode = flag;
+ }
+
+ public synchronized void startHandshake() throws IOException
+ {
+ if (DEBUG_HANDSHAKE_LAYER)
+ {
+ logger.log (Component.SSL_HANDSHAKE, "startHandshake called in {0}",
+ Thread.currentThread());
+ handshakeTime = System.currentTimeMillis();
+ }
+ if (handshakeDone)
+ {
+ if (clientMode)
+ {
+ handshakeDone = false;
+ doClientHandshake();
+ }
+ else
+ {
+ Handshake req = new Handshake(Handshake.Type.HELLO_REQUEST, null);
+ req.write (handshakeOut, session.protocol);
+ handshakeOut.flush();
+// recordOutput.setHandshakeAvail(req.write(handshakeOut, session.protocol));
+ }
+ return;
+ }
+ if (recordInput == null)
+ {
+ setupIO();
+ }
+ if (clientMode)
+ {
+ doClientHandshake();
+ }
+ else
+ {
+ doServerHandshake();
+ }
+ }
+
+ // Socket methods.
+ // -------------------------------------------------------------------------
+
+ public InetAddress getInetAddress()
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.getInetAddress();
+ }
+ else
+ {
+ return super.getInetAddress();
+ }
+ }
+
+ public InetAddress getLocalAddress()
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.getLocalAddress();
+ }
+ else
+ {
+ return super.getLocalAddress();
+ }
+ }
+
+ public int getPort()
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.getPort();
+ }
+ else
+ {
+ return super.getPort();
+ }
+ }
+
+ public int getLocalPort()
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.getLocalPort();
+ }
+ else
+ {
+ return super.getLocalPort();
+ }
+ }
+
+ public InputStream getInputStream() throws IOException
+ {
+ if (applicationIn == null)
+ {
+ setupIO();
+ }
+ return applicationIn;
+ }
+
+ public OutputStream getOutputStream() throws IOException
+ {
+ if (applicationOut == null)
+ {
+ setupIO();
+ }
+ return applicationOut;
+ }
+
+ public void setTcpNoDelay(boolean flag) throws SocketException
+ {
+ if (underlyingSocket != null)
+ {
+ underlyingSocket.setTcpNoDelay(flag);
+ }
+ else
+ {
+ super.setTcpNoDelay(flag);
+ }
+ }
+
+ public boolean getTcpNoDelay() throws SocketException
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.getTcpNoDelay();
+ }
+ else
+ {
+ return super.getTcpNoDelay();
+ }
+ }
+
+ public void setSoLinger(boolean flag, int linger) throws SocketException
+ {
+ if (underlyingSocket != null)
+ {
+ underlyingSocket.setSoLinger(flag, linger);
+ }
+ else
+ {
+ super.setSoLinger(flag, linger);
+ }
+ }
+
+ public int getSoLinger() throws SocketException
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.getSoLinger();
+ }
+ else
+ {
+ return super.getSoLinger();
+ }
+ }
+
+ public void sendUrgentData(int data) throws IOException
+ {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ public void setSoTimeout(int timeout) throws SocketException
+ {
+ if (underlyingSocket != null)
+ {
+ underlyingSocket.setSoTimeout(timeout);
+ }
+ else
+ {
+ super.setSoTimeout(timeout);
+ }
+ }
+
+ public int getSoTimeout() throws SocketException
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.getSoTimeout();
+ }
+ else
+ {
+ return super.getSoTimeout();
+ }
+ }
+
+ public void setSendBufferSize(int size) throws SocketException
+ {
+ if (underlyingSocket != null)
+ {
+ underlyingSocket.setSendBufferSize(size);
+ }
+ else
+ {
+ super.setSendBufferSize(size);
+ }
+ }
+
+ public int getSendBufferSize() throws SocketException
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.getSendBufferSize();
+ }
+ else
+ {
+ return super.getSendBufferSize();
+ }
+ }
+
+ public void setReceiveBufferSize(int size) throws SocketException
+ {
+ if (underlyingSocket != null)
+ {
+ underlyingSocket.setReceiveBufferSize(size);
+ }
+ else
+ {
+ super.setReceiveBufferSize(size);
+ }
+ }
+
+ public int getReceiveBufferSize() throws SocketException
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.getReceiveBufferSize();
+ }
+ else
+ {
+ return super.getReceiveBufferSize();
+ }
+ }
+
+ public synchronized void close() throws IOException
+ {
+ if (recordInput == null)
+ {
+ if (underlyingSocket != null)
+ {
+ if (autoClose)
+ underlyingSocket.close();
+ }
+ else
+ super.close();
+ return;
+ }
+// while (recordOutput.applicationDataPending()) Thread.yield();
+ Alert close = new Alert (Alert.Level.WARNING, Alert.Description.CLOSE_NOTIFY);
+ sendAlert (close);
+ long wait = System.currentTimeMillis() + 60000L;
+ while (session.currentAlert == null && !recordInput.pollClose())
+ {
+
+ Thread.yield();
+ if (wait <= System.currentTimeMillis())
+ {
+ break;
+ }
+ }
+ boolean gotClose = session.currentAlert != null &&
+ session.currentAlert.getDescription() == Alert.Description.CLOSE_NOTIFY;
+// recordInput.setRunning(false);
+// recordOutput.setRunning(false);
+// recordLayer.interrupt();
+ recordInput = null;
+// recordOutput = null;
+// recordLayer = null;
+ if (underlyingSocket != null)
+ {
+ if (autoClose)
+ underlyingSocket.close();
+ }
+ else
+ super.close();
+ if (!gotClose)
+ {
+ session.invalidate();
+ throw new SSLException("did not receive close notify");
+ }
+ }
+
+ public String toString()
+ {
+ if (underlyingSocket != null)
+ {
+ return SSLSocket.class.getName() + " [ " + underlyingSocket + " ]";
+ }
+ else
+ {
+ return SSLSocket.class.getName() + " [ " + super.toString() + " ]";
+ }
+ }
+
+ // Configuration insanity begins here.
+
+ public void connect(SocketAddress saddr) throws IOException
+ {
+ if (underlyingSocket != null)
+ {
+ underlyingSocket.connect(saddr);
+ }
+ else
+ {
+ super.connect(saddr);
+ }
+ }
+
+ public void connect(SocketAddress saddr, int timeout) throws IOException
+ {
+ if (underlyingSocket != null)
+ {
+ underlyingSocket.connect(saddr, timeout);
+ }
+ else
+ {
+ super.connect(saddr, timeout);
+ }
+ }
+
+ public void bind(SocketAddress saddr) throws IOException
+ {
+ if (underlyingSocket != null)
+ {
+ underlyingSocket.bind(saddr);
+ }
+ else
+ {
+ super.bind(saddr);
+ }
+ }
+
+ public SocketAddress getLocalSocketAddress()
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.getLocalSocketAddress();
+ }
+ else
+ {
+ return super.getLocalSocketAddress();
+ }
+ }
+
+ public SocketChannel getChannel()
+ {
+ return channel;
+ }
+
+ public boolean isBound()
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.isBound();
+ }
+ else
+ {
+ return super.isBound();
+ }
+ //throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public boolean isClosed()
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.isClosed();
+ }
+ else
+ {
+ return super.isClosed();
+ }
+ //throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ //public SocketAddress getRemoteSocketAddress()
+ //{
+ // if (underlyingSocket != null)
+ // {
+ // return underlyingSocket.getRemoteSocketAddress();
+ // }
+ // else
+ // {
+ // return super.getRemoteSocketAddress();
+ // }
+ //}
+
+ public void setOOBInline(boolean flag) throws SocketException
+ {
+ //if (underlyingSocket != null)
+ // {
+ // underlyingSocket.setOOBInline(flag);
+ // }
+ //else
+ // {
+ // super.setOOBInline(flag);
+ // }
+ throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public boolean getOOBInline() throws SocketException
+ {
+ //if (underlyingSocket != null)
+ // {
+ // return underlyingSocket.getOOBInline();
+ // }
+ //else
+ // {
+ // return super.getOOBInline();
+ // }
+ throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public void setKeepAlive(boolean flag) throws SocketException
+ {
+ //if (underlyingSocket != null)
+ // {
+ // underlyingSocket.setKeepAlive(flag);
+ // }
+ //else
+ // {
+ // super.setKeepAlive(flag);
+ // }
+ throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public boolean getKeepAlive() throws SocketException
+ {
+ //if (underlyingSocket != null)
+ // {
+ // return underlyingSocket.getKeepAlive();
+ // }
+ //else
+ // {
+ // return super.getKeepAlive();
+ // }
+ throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public void setTrafficClass(int clazz) throws SocketException
+ {
+ //if (underlyingSocket != null)
+ // {
+ // underlyingSocket.setTrafficClass(clazz);
+ // }
+ //else
+ // {
+ // super.setTrafficClass(clazz);
+ // }
+ throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public int getTrafficClass() throws SocketException
+ {
+ //if (underlyingSocket != null)
+ // {
+ // return underlyingSocket.getTrafficClass();
+ // }
+ //else
+ // {
+ // return super.getTrafficClass();
+ // }
+ throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public void setReuseAddress(boolean flag) throws SocketException
+ {
+ //if (underlyingSocket != null)
+ // {
+ // underlyingSocket.setReuseAddress(flag);
+ // }
+ //else
+ // {
+ // super.setReuseAddress(flag);
+ // }
+ throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public boolean getReuseAddress() throws SocketException
+ {
+ //if (underlyingSocket != null)
+ // {
+ // return underlyingSocket.getReuseAddress();
+ // }
+ //else
+ // {
+ // return super.getReuseAddress();
+ // }
+ throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public void shutdownInput() throws IOException
+ {
+ //if (underlyingSocket != null)
+ // {
+ // underlyingSocket.shutdownInput();
+ // }
+ //else
+ // {
+ // super.shutdownInput();
+ // }
+ throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public void shutdownOutput() throws IOException
+ {
+ //if (underlyingSocket != null)
+ // {
+ // underlyingSocket.shutdownOutput();
+ // }
+ //else
+ // {
+ // super.shutdownOutput();
+ // }
+ throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public boolean isConnected()
+ {
+ if (underlyingSocket != null)
+ {
+ return underlyingSocket.isConnected();
+ }
+ else
+ {
+ return super.isConnected();
+ }
+ //throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public boolean isInputShutdown()
+ {
+ //if (underlyingSocket != null)
+ // {
+ // return underlyingSocket.isInputShutdown();
+ // }
+ //else
+ // {
+ // return super.isInputShutdown();
+ // }
+ throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ public boolean isOutputShutdown()
+ {
+ //if (underlyingSocket != null)
+ // {
+ // return underlyingSocket.isOutputShutdown();
+ // }
+ //else
+ // {
+ // return super.isOutputShutdown();
+ // }
+ throw new UnsupportedOperationException("1.4 methods not enabled");
+ }
+
+ protected void finalize()
+ {
+ if (session.currentAlert == null)
+ {
+ try
+ {
+ close();
+ }
+ catch (Exception ignore) { }
+ }
+ }
+
+ // Package methods.
+ // -------------------------------------------------------------------------
+
+ void setSessionContext(SessionContext sessionContext)
+ {
+ this.sessionContext = sessionContext;
+ }
+
+ void setEnabledCipherSuites(List suites)
+ {
+ session.enabledSuites = suites;
+ }
+
+ void setEnabledProtocols(SortedSet protocols)
+ {
+ session.enabledProtocols = protocols;
+ }
+
+ void setSRPTrustManager(SRPTrustManager srpTrustManager)
+ {
+ session.srpTrustManager = srpTrustManager;
+ }
+
+ void setTrustManager(X509TrustManager trustManager)
+ {
+ session.trustManager = trustManager;
+ }
+
+ void setKeyManager(X509KeyManager keyManager)
+ {
+ session.keyManager = keyManager;
+ }
+
+ void setRandom(SecureRandom random)
+ {
+ session.random = random;
+ }
+
+ void sendAlert (Alert alert) throws IOException
+ {
+ RecordOutputStream out =
+ new RecordOutputStream (socketOut, ContentType.ALERT, session.params);
+ out.write (alert.getEncoded ());
+ }
+
+ /**
+ * Gets the most-recently-received alert message.
+ *
+ * @return The alert message.
+ */
+ Alert checkAlert()
+ {
+ return session.currentAlert;
+ }
+
+ synchronized void checkHandshakeDone() throws IOException
+ {
+ if (!handshakeDone)
+ {
+ startHandshake();
+ }
+ Alert alert = session.currentAlert;
+ if (alert != null && alert.getLevel() == Alert.Level.FATAL)
+ {
+ throw new AlertException(alert, false);
+ }
+ if (handshakeIn.available() > 0 && !clientMode)
+ {
+ handshakeDone = false;
+ startHandshake();
+ }
+ }
+
+ // Own methods.
+ // -------------------------------------------------------------------------
+
+ private static final byte[] SENDER_CLIENT =
+ new byte[] { 0x43, 0x4C, 0x4E, 0x54 };
+ private static final byte[] SENDER_SERVER =
+ new byte[] { 0x53, 0x52, 0x56, 0x52 };
+
+ private void changeCipherSpec () throws IOException
+ {
+ RecordOutputStream out =
+ new RecordOutputStream (socketOut, ContentType.CHANGE_CIPHER_SPEC, session.params);
+ out.write (1);
+ }
+
+ private void readChangeCipherSpec () throws IOException
+ {
+ RecordInputStream in =
+ new RecordInputStream (recordInput, ContentType.CHANGE_CIPHER_SPEC);
+ if (in.read() != 1)
+ {
+ throw new SSLProtocolException ("bad change cipher spec message");
+ }
+ }
+
+ /**
+ * Initializes the application data streams and starts the record layer
+ * threads.
+ */
+ private synchronized void setupIO() throws IOException
+ {
+ if (recordInput != null)
+ {
+ return;
+ }
+ if (underlyingSocket != null)
+ {
+ socketIn = underlyingSocket.getInputStream();
+ socketOut = underlyingSocket.getOutputStream();
+ }
+ else
+ {
+ socketIn = super.getInputStream();
+ socketOut = super.getOutputStream();
+ }
+// recordLayer = new ThreadGroup("record_layer");
+// recordInput = new RecordInput(in, session, recordLayer);
+// recordOutput = new RecordOutput(out, session, recordLayer);
+// recordInput.setRecordOutput(recordOutput);
+// recordLayer.setDaemon(true);
+// recordInput.start();
+// recordOutput.start();
+ recordInput = new RecordInput (socketIn, session);
+ applicationIn = new SSLSocketInputStream(
+ new RecordInputStream (recordInput, ContentType.APPLICATION_DATA), this);
+ applicationOut = new SSLSocketOutputStream(
+ new RecordOutputStream (socketOut, ContentType.APPLICATION_DATA, session.params), this);
+ handshakeIn = new SSLSocketInputStream(
+ new RecordInputStream (recordInput, ContentType.HANDSHAKE), this, false);
+ handshakeOut = new BufferedOutputStream (new SSLSocketOutputStream(
+ new RecordOutputStream (socketOut, ContentType.HANDSHAKE, session.params), this, false), 8096);
+ }
+
+ private void handshakeCompleted ()
+ {
+ handshakeDone = true;
+ HandshakeCompletedEvent event = new HandshakeCompletedEvent (this, session);
+ for (Iterator it = handshakeListeners.iterator (); it.hasNext (); )
+ {
+ try
+ {
+ ((HandshakeCompletedListener) it.next ()).handshakeCompleted (event);
+ }
+ catch (Throwable t) { }
+ }
+ if (createSessions)
+ {
+ synchronized (session)
+ {
+ sessionContext.addSession (session.sessionId, session);
+ session.access ();
+ }
+ }
+
+ if (DEBUG_HANDSHAKE_LAYER)
+ {
+ logger.log (Component.SSL_HANDSHAKE, "Handshake finished in {0}",
+ Thread.currentThread());
+ handshakeTime = System.currentTimeMillis() - handshakeTime;
+ logger.log (Component.SSL_HANDSHAKE, "Elapsed time {0}s",
+ new Long (handshakeTime / 1000));
+ }
+ }
+
+ /*
+ * Perform the client handshake. The process looks like this:
+ *
+ * ClientHello -->
+ * ServerHello <--
+ * Certificate* <--
+ * ServerKeyExchange* <--
+ * CertificateRequest* <--
+ * ServerHelloDone* <--
+ * Certificate* -->
+ * ClientKeyExchange -->
+ * CertificateVerify* -->
+ * [ChangeCipherSpec] -->
+ * Finished -->
+ * [ChangeCipherSpec] <--
+ * Finished <--
+ *
+ * With --> denoting output and <-- denoting input. * denotes optional
+ * messages.
+ *
+ * Alternatively, this may be an abbreviated handshake if we are resuming
+ * a session:
+ *
+ * ClientHello -->
+ * ServerHello <--
+ * [ChangeCipherSpec] <--
+ * Finished <--
+ * [ChangeCipherSpec] -->
+ * Finished -->
+ */
+ private void doClientHandshake() throws IOException
+ {
+ if (DEBUG_HANDSHAKE_LAYER)
+ {
+ logger.log (Component.SSL_HANDSHAKE, "starting client handshake in {0}",
+ Thread.currentThread());
+ }
+
+ IMessageDigest md5 = HashFactory.getInstance(Registry.MD5_HASH);
+ IMessageDigest sha = HashFactory.getInstance(Registry.SHA160_HASH);
+ DigestInputStream din = new DigestInputStream(handshakeIn, md5, sha);
+ DigestOutputStream dout = new DigestOutputStream(handshakeOut, md5, sha);
+ Session continuedSession = null;
+ byte[] sessionId = new byte[0];
+ List extensions = null;
+ String user = null;
+ CertificateType certType = CertificateType.X509;
+
+ // Look through the available sessions to see if an appropriate one is
+ // available.
+ for (Enumeration e = sessionContext.getIds(); e.hasMoreElements(); )
+ {
+ byte[] id = (byte[]) e.nextElement();
+ continuedSession = (Session) sessionContext.getSession(id);
+ if (continuedSession == null)
+ {
+ continue;
+ }
+ if (!session.enabledProtocols.contains(continuedSession.protocol))
+ {
+ continue;
+ }
+ if (continuedSession.getPeerHost().equals(remoteHost))
+ {
+ sessionId = id;
+ break;
+ }
+ }
+
+ // If a SRP suite is enabled, ask for a username so we can include it
+ // with our extensions list.
+ for (Iterator i = session.enabledSuites.iterator(); i.hasNext(); )
+ {
+ CipherSuite s = (CipherSuite) i.next();
+ if (s.getKeyExchange() == "SRP")
+ {
+ extensions = new LinkedList();
+ user = askUserName(remoteHost);
+ byte[] b = user.getBytes("UTF-8");
+ if (b.length > 255)
+ {
+ handshakeFailure();
+ throw new SSLException("SRP username too long");
+ }
+ extensions.add(new Extension(Extension.Type.SRP,
+ Util.concat(new byte[] { (byte) b.length }, b)));
+
+ break;
+ }
+ }
+
+ // If the jessie.fragment.length property is set, add the appropriate
+ // extension to the list. The fragment length is only actually set if
+ // the server responds with the same extension.
+ try
+ {
+ int flen = Integer.parseInt(Util.getSecurityProperty("jessie.fragment.length"));
+ byte[] ext = new byte[1];
+ if (flen == 512)
+ ext[0] = 1;
+ else if (flen == 1024)
+ ext[0] = 2;
+ else if (flen == 2048)
+ ext[0] = 3;
+ else if (flen == 4096)
+ ext[0] = 4;
+ else
+ throw new NumberFormatException();
+ if (extensions == null)
+ extensions = new LinkedList();
+ extensions.add(new Extension(Extension.Type.MAX_FRAGMENT_LENGTH, ext));
+ }
+ catch (NumberFormatException nfe) { }
+
+ // FIXME: set certificate types.
+
+ // Send the client hello.
+ ProtocolVersion version = session.protocol;
+ Random clientRandom =
+ new Random(Util.unixTime(), session.random.generateSeed(28));
+ session.protocol = (ProtocolVersion) session.enabledProtocols.last();
+ List comp = new ArrayList(2);
+ comp.add(CompressionMethod.ZLIB);
+ comp.add(CompressionMethod.NULL);
+ ClientHello clientHello =
+ new ClientHello(session.protocol, clientRandom, sessionId,
+ session.enabledSuites, comp, extensions);
+ Handshake msg = new Handshake(Handshake.Type.CLIENT_HELLO, clientHello);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ msg.write (dout, version);
+// recordOutput.setHandshakeAvail(msg.write(dout, version));
+ dout.flush();
+// try
+// {
+// Thread.sleep(150);
+// }
+// catch (InterruptedException ie)
+// {
+// }
+
+ // Receive the server hello.
+ msg = Handshake.read(din);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ if (msg.getType() != Handshake.Type.SERVER_HELLO)
+ {
+ throwUnexpectedMessage();
+ }
+ ServerHello serverHello = (ServerHello) msg.getBody();
+ Random serverRandom = serverHello.getRandom();
+ version = serverHello.getVersion();
+
+ // If we don't directly support the server's protocol version, choose
+ // the highest one we support that is less than the server's version.
+ if (!session.enabledProtocols.contains(version))
+ {
+ ProtocolVersion v1 = null, v2 = null;
+ for (Iterator it = session.enabledProtocols.iterator();
+ it.hasNext(); )
+ {
+ v1 = (ProtocolVersion) it.next();
+ if (v1.compareTo(version) > 0)
+ break;
+ v2 = v1;
+ }
+ version = v1;
+ }
+
+ // The server's version is either unsupported by us (unlikely) or the user
+ // has only enabled incompatible versions.
+ if (version == null)
+ {
+ Alert.Description desc = null;
+ if (serverHello.getVersion() == ProtocolVersion.SSL_3)
+ {
+ desc = Alert.Description.HANDSHAKE_FAILURE;
+ }
+ else
+ {
+ desc = Alert.Description.PROTOCOL_VERSION;
+ }
+ Alert alert = new Alert(Alert.Level.FATAL, desc);
+ sendAlert(alert);
+ session.currentAlert = alert;
+ fatal();
+ throw new AlertException(alert, true);
+ }
+
+ if (serverHello.getExtensions() != null)
+ {
+ for (Iterator it = serverHello.getExtensions().iterator();
+ it.hasNext(); )
+ {
+ Extension e = (Extension) it.next();
+ if (e.getType() == Extension.Type.MAX_FRAGMENT_LENGTH)
+ {
+ int len = Extensions.getMaxFragmentLength(e).intValue();
+ session.params.setFragmentLength(len);
+// recordOutput.setFragmentLength(len);
+// recordInput.setFragmentLength(len);
+ }
+ else if (e.getType() == Extension.Type.CERT_TYPE)
+ {
+ certType = Extensions.getServerCertType(e);
+ }
+ }
+ }
+
+ CipherSuite suite = serverHello.getCipherSuite().resolve(version);
+ boolean newSession = true;
+ if (sessionId.length > 0 &&
+ Arrays.equals(sessionId, serverHello.getSessionId()))
+ {
+ SecurityParameters params = session.params;
+ SecureRandom random = session.random;
+ session = (Session) continuedSession.clone();
+ session.params = params;
+ session.random = random;
+ recordInput.setSession(session);
+// recordOutput.setSession(session);
+ suite = session.cipherSuite;
+ newSession = false;
+ }
+ else
+ {
+ sessionContext.removeSession(new Session.ID(sessionId));
+ }
+ if (newSession)
+ {
+ session.peerHost = remoteHost;
+ session.sessionId = new Session.ID(serverHello.getSessionId());
+ session.cipherSuite = suite;
+ }
+ session.params.reset();
+// session.params.setInMac(null);
+// session.params.setOutMac(null);
+// session.params.setInRandom(null);
+// session.params.setOutRandom(null);
+// session.params.setInCipher(null);
+// session.params.setOutCipher(null);
+ session.currentAlert = null;
+ session.valid = true;
+ session.protocol = version;
+
+ // If the server responded with the same session id that we sent, we
+ // assume that the session will be continued, and skip the bulk of the
+ // handshake.
+ if (newSession)
+ {
+ PublicKey serverKey = null, serverKex = null;
+ KeyPair clientKeys = null, clientKex = null;
+ CertificateRequest certReq;
+ boolean sendKeyExchange = false;
+ BigInteger srp_x = null;
+ IKeyAgreementParty clientKA = null;
+ IncomingMessage in; // used for key agreement protocol exchange
+ OutgoingMessage out = null;
+
+ if (suite.getKeyExchange() == "SRP")
+ {
+ String password = askPassword(user);
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE,
+ "SRP: password read is ''{0}''", password);
+ }
+ byte[] userSrpPassword = password.getBytes("UTF-8");
+
+ // instantiate and setup client-side key agreement party
+ clientKA = KeyAgreementFactory.getPartyAInstance(Registry.SRP_TLS_KA);
+ Map clientAttributes = new HashMap();
+ clientAttributes.put(SRP6KeyAgreement.HASH_FUNCTION,
+ Registry.SHA160_HASH);
+ clientAttributes.put(SRP6KeyAgreement.USER_IDENTITY, user);
+ clientAttributes.put(SRP6KeyAgreement.USER_PASSWORD, userSrpPassword);
+ try
+ {
+ clientKA.init(clientAttributes);
+ // initiate the exchange
+ out = clientKA.processMessage(null);
+ }
+ catch (KeyAgreementException x)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "SRP exception", x);
+ }
+ throwHandshakeFailure();
+ }
+ }
+
+ if (suite.getSignature() != "anon")
+ {
+ msg = Handshake.read(din, certType);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ if (msg.getType() != Handshake.Type.CERTIFICATE)
+ {
+ throwUnexpectedMessage();
+ }
+ Certificate serverCertificate = (Certificate) msg.getBody();
+ X509Certificate[] peerCerts = serverCertificate.getCertificates();
+ try
+ {
+ session.trustManager.checkServerTrusted(peerCerts,
+ suite.getAuthType());
+ if (suite.getSignature() == "RSA" &&
+ !(peerCerts[0].getPublicKey() instanceof RSAPublicKey))
+ throw new InvalidKeyException("improper public key");
+ if (suite.getKeyExchange() == "DH" &&
+ !(peerCerts[0].getPublicKey() instanceof DHPublicKey))
+ throw new InvalidKeyException("improper public key");
+ if (suite.getKeyExchange() == "DHE")
+ {
+ if (suite.getSignature() == "RSA" &&
+ !(peerCerts[0].getPublicKey() instanceof RSAPublicKey))
+ throw new InvalidKeyException("improper public key");
+ if (suite.getSignature() == "DSS" &&
+ !(peerCerts[0].getPublicKey() instanceof DSAPublicKey))
+ throw new InvalidKeyException("improper public key");
+ }
+ session.peerCerts = peerCerts;
+ session.peerVerified = true;
+ }
+ catch (InvalidKeyException ike)
+ {
+ throwHandshakeFailure();
+ }
+ catch (Exception x)
+ {
+ if (!checkCertificates(peerCerts))
+ {
+ peerUnverified(peerCerts);
+ SSLPeerUnverifiedException e =
+ new SSLPeerUnverifiedException ("could not verify peer certificate: "+
+ peerCerts[0].getSubjectDN());
+ e.initCause (x);
+ throw e;
+ }
+ session.peerCerts = peerCerts;
+ session.peerVerified = true;
+ }
+ serverKey = peerCerts[0].getPublicKey();
+ serverKex = serverKey;
+ }
+
+ msg = Handshake.read(din, suite, serverKey);
+
+ // Receive the server's key exchange.
+ if (msg.getType() == Handshake.Type.SERVER_KEY_EXCHANGE)
+ {
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ ServerKeyExchange skex = (ServerKeyExchange) msg.getBody();
+ serverKex = skex.getPublicKey();
+ if (suite.getSignature() != "anon")
+ {
+ ISignature sig = null;
+ if (suite.getSignature() == "RSA")
+ {
+ sig = new SSLRSASignature();
+ }
+ else if (suite.getSignature() == "DSS")
+ {
+ sig = SignatureFactory.getInstance(Registry.DSS_SIG);
+ }
+ sig.setupVerify(Collections.singletonMap(
+ ISignature.VERIFIER_KEY, serverKey));
+ byte[] buf = clientRandom.getEncoded();
+ sig.update(buf, 0, buf.length);
+ buf = serverRandom.getEncoded();
+ sig.update(buf, 0, buf.length);
+ if (suite.getKeyExchange() == "RSA")
+ {
+ updateSig(sig, ((RSAPublicKey) serverKex).getModulus());
+ updateSig(sig, ((RSAPublicKey) serverKex).getPublicExponent());
+ }
+ else if (suite.getKeyExchange() == "DHE")
+ {
+ updateSig(sig, ((DHPublicKey) serverKex).getParams().getP());
+ updateSig(sig, ((DHPublicKey) serverKex).getParams().getG());
+ updateSig(sig, ((DHPublicKey) serverKex).getY());
+ }
+ else if (suite.getKeyExchange() == "SRP")
+ {
+ updateSig(sig, ((SRPPublicKey) serverKex).getN());
+ updateSig(sig, ((SRPPublicKey) serverKex).getG());
+ byte[] srpSalt = skex.getSRPSalt();
+ sig.update((byte) srpSalt.length);
+ sig.update(srpSalt, 0, srpSalt.length);
+ updateSig(sig, ((SRPPublicKey) serverKex).getY());
+ }
+ if (!sig.verify(skex.getSignature().getSigValue()))
+ {
+ throwHandshakeFailure();
+ }
+ }
+
+ if (suite.getKeyExchange() == "SRP")
+ {
+ // use server's key exchange data to continue
+ // agreement protocol by faking a received incoming
+ // message. again the following code can be broken
+ // into multiple blocks for more accurate exception
+ // handling
+ try
+ {
+ out = new OutgoingMessage();
+ out.writeMPI(((SRPPublicKey) serverKex).getN());
+ out.writeMPI(((SRPPublicKey) serverKex).getG());
+ out.writeMPI(new BigInteger(1, skex.getSRPSalt()));
+ out.writeMPI(((SRPPublicKey) serverKex).getY());
+
+ in = new IncomingMessage(out.toByteArray());
+
+ out = clientKA.processMessage(in);
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "clientKA isComplete? {0}",
+ Boolean.valueOf (clientKA.isComplete()));
+ }
+ }
+ catch (KeyAgreementException x)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "SRP exception", x);
+ }
+ throwHandshakeFailure();
+ }
+ }
+ msg = Handshake.read(din, suite, serverKey);
+ }
+
+ // See if the server wants us to send our certificates.
+ certReq = null;
+ if (msg.getType() == Handshake.Type.CERTIFICATE_REQUEST)
+ {
+ if (suite.getSignature() == "anon")
+ {
+ throwHandshakeFailure();
+ }
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ certReq = (CertificateRequest) msg.getBody();
+ msg = Handshake.read(din);
+ }
+
+ // Read ServerHelloDone.
+ if (msg.getType() != Handshake.Type.SERVER_HELLO_DONE)
+ {
+ throwUnexpectedMessage();
+ }
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+
+ // Send our certificate chain if the server asked for it.
+ if (certReq != null)
+ {
+ String alias = session.keyManager.chooseClientAlias(
+ certReq.getTypeStrings(), certReq.getAuthorities(), null);
+ if (alias == null && version == ProtocolVersion.SSL_3)
+ {
+ Alert alert =
+ new Alert(Alert.Level.WARNING, Alert.Description.NO_CERTIFICATE);
+ sendAlert(alert);
+ }
+ else
+ {
+ X509Certificate[] chain =
+ session.keyManager.getCertificateChain(alias);
+ PrivateKey key = session.keyManager.getPrivateKey(alias);
+ if (chain == null)
+ {
+ chain = new X509Certificate[0];
+ }
+ Certificate cert = new Certificate(chain);
+ msg = new Handshake(Handshake.Type.CERTIFICATE, cert);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ msg.write(dout, version);
+// recordOutput.setHandshakeAvail(msg.write(dout, version));;
+ dout.flush();
+ if (chain.length > 0)
+ {
+ session.localCerts = chain;
+ clientKeys = new KeyPair(chain[0].getPublicKey(), key);
+ }
+ }
+ }
+
+ // Send our key exchange.
+ byte[] preMasterSecret = null;
+ ClientKeyExchange ckex = null;
+ if (suite.getKeyExchange() == "RSA")
+ {
+ ProtocolVersion v =
+ (ProtocolVersion) session.enabledProtocols.last();
+ byte[] b = new byte[46];
+ session.random.nextBytes (b);
+ preMasterSecret = Util.concat(v.getEncoded(), b);
+ EME_PKCS1_V1_5 pkcs1 = EME_PKCS1_V1_5.getInstance((RSAPublicKey) serverKex);
+ BigInteger bi = new BigInteger(1,
+ pkcs1.encode(preMasterSecret, session.random));
+ bi = RSA.encrypt((RSAPublicKey) serverKex, bi);
+ ckex = new ClientKeyExchange(Util.trim(bi));
+ }
+ else if (suite.getKeyExchange().startsWith("DH"))
+ {
+ if (clientKeys == null ||
+ !(clientKeys.getPublic() instanceof DHPublicKey))
+ {
+ GnuDHPrivateKey tmpKey =
+ new GnuDHPrivateKey(null, ((DHPublicKey) serverKex).getParams().getP(),
+ ((DHPublicKey) serverKex).getParams().getG(), null);
+ clientKA = KeyAgreementFactory.getPartyBInstance(Registry.DH_KA);
+ Map attr = new HashMap();
+ attr.put(DiffieHellmanKeyAgreement.KA_DIFFIE_HELLMAN_OWNER_PRIVATE_KEY,
+ tmpKey);
+ attr.put(DiffieHellmanKeyAgreement.SOURCE_OF_RANDOMNESS,
+ session.random);
+ try
+ {
+ clientKA.init(attr);
+ out = new OutgoingMessage();
+ out.writeMPI(((DHPublicKey) serverKex).getY());
+ in = new IncomingMessage(out.toByteArray());
+ out = clientKA.processMessage(in);
+ in = new IncomingMessage(out.toByteArray());
+ ckex = new ClientKeyExchange(in.readMPI());
+ }
+ catch (KeyAgreementException kae)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "DH exception", kae);
+ }
+ internalError();
+ RuntimeException re = new RuntimeException (kae.getMessage());
+ re.initCause (kae);
+ throw re;
+ }
+ }
+ else
+ {
+ clientKA = KeyAgreementFactory.getPartyBInstance(Registry.ELGAMAL_KA);
+ Map attr = new HashMap();
+ attr.put(ElGamalKeyAgreement.KA_ELGAMAL_RECIPIENT_PRIVATE_KEY,
+ clientKeys.getPrivate());
+ try
+ {
+ // The key exchange is already complete here; our public
+ // value was sent with our certificate.
+ clientKA.init(attr);
+ }
+ catch (KeyAgreementException kae)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ logger.log (Component.SSL_KEY_EXCHANGE, "DH exception", kae);
+ internalError();
+ RuntimeException re = new RuntimeException (kae.getMessage());
+ re.initCause (kae);
+ throw re;
+ }
+ ckex = new ClientKeyExchange(new byte[0]);
+ }
+ }
+ else if (suite.getKeyExchange() == "SRP")
+ {
+ // at this point, out --the outgoing message-- already contains
+ // what we want. so...
+ BigInteger A = null;
+ try
+ {
+ in = new IncomingMessage(out.toByteArray());
+ A = in.readMPI();
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "client A:{0}", A);
+ }
+ }
+ catch (KeyAgreementException x)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "SRP exception", x);
+ }
+ throwHandshakeFailure();
+ }
+ ckex = new ClientKeyExchange(A);
+ }
+ msg = new Handshake(Handshake.Type.CLIENT_KEY_EXCHANGE, ckex);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ msg.write (dout, version);
+// recordOutput.setHandshakeAvail(msg.write(dout, version));;
+
+ // Generate the master secret.
+ if (suite.getKeyExchange().startsWith("DH"))
+ {
+ try
+ {
+ preMasterSecret = clientKA.getSharedSecret();
+ }
+ catch (KeyAgreementException kae)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "DH exception", kae);
+ }
+ internalError();
+ RuntimeException re = new RuntimeException (kae.getMessage());
+ re.initCause (kae);
+ throw re;
+ }
+ }
+ else if (suite.getKeyExchange() == "SRP")
+ {
+ try
+ {
+ preMasterSecret = clientKA.getSharedSecret();
+ }
+ catch (KeyAgreementException x)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "SRP exception", x);
+ }
+ throwHandshakeFailure();
+ }
+ finally
+ {
+ clientKA = null;
+ }
+ }
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "preMasterSecret:\n{0}",
+ Util.toHexString (preMasterSecret, ':'));
+ logger.log (Component.SSL_KEY_EXCHANGE, "client.random:\n{0}",
+ Util.toHexString(clientRandom.getEncoded(), ':'));
+ logger.log (Component.SSL_KEY_EXCHANGE, "server.random:\n{0}",
+ Util.toHexString(serverRandom.getEncoded(), ':'));
+ }
+ IRandom genSecret = null;
+ if (version == ProtocolVersion.SSL_3)
+ {
+ genSecret = new SSLRandom();
+ HashMap attr = new HashMap();
+ attr.put(SSLRandom.SECRET, preMasterSecret);
+ attr.put(SSLRandom.SEED,
+ Util.concat(clientRandom.getEncoded(), serverRandom.getEncoded()));
+ genSecret.init(attr);
+ }
+ else
+ {
+ genSecret = new TLSRandom();
+ HashMap attr = new HashMap();
+ attr.put(TLSRandom.SECRET, preMasterSecret);
+ attr.put(TLSRandom.SEED,
+ Util.concat(("master secret").getBytes("UTF-8"),
+ Util.concat(clientRandom.getEncoded(), serverRandom.getEncoded())));
+ genSecret.init(attr);
+ }
+ session.masterSecret = new byte[48];
+ try
+ {
+ genSecret.nextBytes(session.masterSecret, 0, 48);
+ for (int i = 0; i < preMasterSecret.length; i++)
+ {
+ preMasterSecret[i] = 0;
+ }
+ }
+ catch (LimitReachedException shouldNotHappen)
+ {
+ internalError();
+ RuntimeException re = new RuntimeException (shouldNotHappen.getMessage());
+ re.initCause (shouldNotHappen);
+ throw re;
+ }
+
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "masterSecret: {0}",
+ Util.toHexString(session.masterSecret, ':'));
+ }
+
+ // Send our certificate verify message.
+ if (certReq != null && clientKeys != null)
+ {
+ IMessageDigest vMD5 = (IMessageDigest) md5.clone();
+ IMessageDigest vSHA = (IMessageDigest) sha.clone();
+ PrivateKey key = clientKeys.getPrivate();
+ Object sig = null;
+ String sigAlg = null;
+ try
+ {
+ if (key instanceof DSAPrivateKey)
+ {
+ sig = DSSSignature.sign((DSAPrivateKey) key, vSHA.digest(),
+ session.random);
+ sigAlg = "DSS";
+ }
+ else if (key instanceof RSAPrivateKey)
+ {
+ SSLRSASignature rsa = new SSLRSASignature(vMD5, vSHA);
+ rsa.setupSign(Collections.singletonMap(ISignature.SIGNER_KEY, key));
+ sig = rsa.sign();
+ sigAlg = "RSA";
+ }
+ else
+ {
+ throw new InvalidKeyException("no appropriate key");
+ }
+ }
+ catch (Exception x)
+ {
+ throwHandshakeFailure();
+ }
+ CertificateVerify verify = new CertificateVerify(sig, sigAlg);
+ msg = new Handshake(Handshake.Type.CERTIFICATE_VERIFY, verify);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ msg.write(dout, version);
+// recordOutput.setHandshakeAvail(msg.write(dout, version));;
+ }
+ dout.flush();
+ }
+
+ byte[][] keys = null;
+ try
+ {
+ keys = generateKeys(serverRandom.getEncoded(),
+ clientRandom.getEncoded(), version);
+ }
+ catch (Exception x)
+ {
+ internalError();
+ RuntimeException re = new RuntimeException (x.getMessage());
+ re.initCause (x);
+ throw re;
+ }
+
+ session.params.setVersion (version);
+
+ // Initialize the algorithms with the derived keys.
+ Object readMac = null, writeMac = null;
+ Object readCipher = null, writeCipher = null;
+ try
+ {
+ if (session.params instanceof GNUSecurityParameters)
+ {
+ HashMap attr = new HashMap();
+ writeMac = CipherSuite.getMac(suite.getMac());
+ readMac = CipherSuite.getMac(suite.getMac());
+ attr.put(IMac.MAC_KEY_MATERIAL, keys[0]);
+ ((IMac) writeMac).init(attr);
+ attr.put(IMac.MAC_KEY_MATERIAL, keys[1]);
+ ((IMac) readMac).init(attr);
+ if (suite.getCipher() == "RC4")
+ {
+ writeCipher = new ARCFour();
+ readCipher = new ARCFour();
+ attr.clear();
+ attr.put(ARCFour.ARCFOUR_KEY_MATERIAL, keys[2]);
+ ((ARCFour) writeCipher).init(attr);
+ attr.put(ARCFour.ARCFOUR_KEY_MATERIAL, keys[3]);
+ ((ARCFour) readCipher).init(attr);
+ }
+ else if (!suite.isStreamCipher())
+ {
+ writeCipher = CipherSuite.getCipher(suite.getCipher());
+ readCipher = CipherSuite.getCipher(suite.getCipher());
+ attr.clear();
+ attr.put(IMode.KEY_MATERIAL, keys[2]);
+ attr.put(IMode.IV, keys[4]);
+ attr.put(IMode.STATE, new Integer(IMode.ENCRYPTION));
+ ((IMode) writeCipher).init(attr);
+ attr.put(IMode.KEY_MATERIAL, keys[3]);
+ attr.put(IMode.IV, keys[5]);
+ attr.put(IMode.STATE, new Integer(IMode.DECRYPTION));
+ ((IMode) readCipher).init(attr);
+ }
+ }
+ else // JCESecurityParameters
+ {
+ writeMac = CipherSuite.getJCEMac (suite.getMac());
+ readMac = CipherSuite.getJCEMac (suite.getMac());
+ writeCipher = CipherSuite.getJCECipher (suite.getCipher());
+ readCipher = CipherSuite.getJCECipher (suite.getCipher());
+ ((Mac) writeMac).init (new SecretKeySpec (keys[0], suite.getMac()));
+ ((Mac) readMac).init (new SecretKeySpec (keys[1], suite.getMac()));
+ if (!suite.isStreamCipher())
+ {
+ ((Cipher) writeCipher).init (Cipher.ENCRYPT_MODE,
+ new SecretKeySpec (keys[2], suite.getCipher()),
+ new IvParameterSpec (keys[4]));
+ ((Cipher) readCipher).init (Cipher.DECRYPT_MODE,
+ new SecretKeySpec (keys[3], suite.getCipher()),
+ new IvParameterSpec (keys[5]));
+ }
+ else
+ {
+ ((Cipher) writeCipher).init (Cipher.ENCRYPT_MODE,
+ new SecretKeySpec (keys[2], suite.getCipher()));
+ ((Cipher) readCipher).init (Cipher.DECRYPT_MODE,
+ new SecretKeySpec (keys[3], suite.getCipher()));
+ }
+ }
+ }
+ // These should technically never happen, if our key generation is not
+ // broken.
+ catch (InvalidKeyException ike)
+ {
+ internalError();
+ RuntimeException re = new RuntimeException (ike.getMessage());
+ re.initCause(ike);
+ throw re;
+ }
+ catch (InvalidAlgorithmParameterException iape)
+ {
+ internalError();
+ RuntimeException re = new RuntimeException (iape.getMessage());
+ re.initCause (iape);
+ throw re;
+ }
+ // These indicate a configuration error with the JCA.
+ catch (NoSuchAlgorithmException nsae)
+ {
+ session.enabledSuites.remove (suite);
+ internalError();
+ SSLException x = new SSLException ("suite " + suite + " not available in this configuration");
+ x.initCause (nsae);
+ throw x;
+ }
+ catch (NoSuchPaddingException nspe)
+ {
+ session.enabledSuites.remove (suite);
+ internalError();
+ SSLException x = new SSLException ("suite " + suite + " not available in this configuration");
+ x.initCause (nspe);
+ throw x;
+ }
+
+ Finished finis = null;
+
+ if (newSession)
+ {
+ changeCipherSpec();
+ session.params.setDeflating(serverHello.getCompressionMethod() == CompressionMethod.ZLIB);
+ session.params.setOutMac(writeMac);
+ session.params.setOutCipher(writeCipher);
+ finis = generateFinished(version, (IMessageDigest) md5.clone(),
+ (IMessageDigest) sha.clone(), true);
+ msg = new Handshake(Handshake.Type.FINISHED, finis);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ msg.write(dout, version);
+ dout.flush();
+ }
+
+ if (session.currentAlert != null &&
+ session.currentAlert.getLevel() == Alert.Level.FATAL)
+ {
+ fatal();
+ throw new AlertException(session.currentAlert, false);
+ }
+
+ synchronized (session.params)
+ {
+ readChangeCipherSpec ();
+ session.params.setInflating(serverHello.getCompressionMethod() == CompressionMethod.ZLIB);
+ session.params.setInMac(readMac);
+ session.params.setInCipher(readCipher);
+ session.params.notifyAll();
+ }
+
+ Finished verify = generateFinished(version, (IMessageDigest) md5.clone(),
+ (IMessageDigest) sha.clone(), false);
+
+ msg = Handshake.read(din, suite, null);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ if (msg.getType() != Handshake.Type.FINISHED)
+ {
+ throwUnexpectedMessage();
+ }
+ finis = (Finished) msg.getBody();
+ if (version == ProtocolVersion.SSL_3)
+ {
+ if (!Arrays.equals(finis.getMD5Hash(), verify.getMD5Hash()) ||
+ !Arrays.equals(finis.getSHAHash(), verify.getSHAHash()))
+ {
+ throwHandshakeFailure();
+ }
+ }
+ else
+ {
+ if (!Arrays.equals(finis.getVerifyData(), verify.getVerifyData()))
+ {
+ throwHandshakeFailure();
+ }
+ }
+
+ if (!newSession)
+ {
+ changeCipherSpec();
+ session.params.setDeflating(serverHello.getCompressionMethod() == CompressionMethod.ZLIB);
+ session.params.setOutMac(writeMac);
+ session.params.setOutCipher(writeCipher);
+ finis = generateFinished(version, md5, sha, true);
+ msg = new Handshake(Handshake.Type.FINISHED, finis);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ msg.write(dout, version);
+ dout.flush();
+ }
+
+ handshakeCompleted();
+ }
+
+ /**
+ * Perform the server handshake.
+ */
+ private void doServerHandshake() throws IOException
+ {
+ if (DEBUG_HANDSHAKE_LAYER)
+ {
+ logger.log (Component.SSL_HANDSHAKE, "doing server handshake in {0}",
+ Thread.currentThread());
+ }
+
+ if (remoteHost == null)
+ {
+ remoteHost = getInetAddress().getHostName();
+ }
+ if (remoteHost == null)
+ {
+ remoteHost = getInetAddress().getHostAddress();
+ }
+
+ IMessageDigest md5 = HashFactory.getInstance(Registry.MD5_HASH);
+ IMessageDigest sha = HashFactory.getInstance(Registry.SHA160_HASH);
+ DigestInputStream din = new DigestInputStream(handshakeIn, md5, sha);
+ DigestOutputStream dout = new DigestOutputStream(handshakeOut, md5, sha);
+
+ // Read the client hello.
+ Handshake msg = Handshake.read(din);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ if (msg.getType() != Handshake.Type.CLIENT_HELLO)
+ {
+ throwUnexpectedMessage();
+ }
+ ClientHello clientHello = (ClientHello) msg.getBody();
+ Random clientRandom = clientHello.getRandom();
+ ProtocolVersion version = clientHello.getVersion();
+ ProtocolVersion server =
+ (ProtocolVersion) session.enabledProtocols.last();
+ CompressionMethod comp;
+ if (clientHello.getCompressionMethods().contains(CompressionMethod.ZLIB))
+ comp = CompressionMethod.ZLIB;
+ else
+ comp = CompressionMethod.NULL;
+ if (!session.enabledProtocols.contains(version)
+ && version.compareTo(server) < 0)
+ {
+ Alert alert = new Alert(Alert.Level.FATAL,
+ Alert.Description.PROTOCOL_VERSION);
+ sendAlert(alert);
+ session.currentAlert = alert;
+ throw new AlertException(alert, true);
+ }
+
+ // Look through the extensions sent by the client (if any), and react to
+ // them appropriately.
+ List extensions = null;
+ String remoteUser = null;
+ if (clientHello.getExtensions() != null)
+ {
+ for (Iterator it = clientHello.getExtensions().iterator(); it.hasNext();)
+ {
+ Extension ex = (Extension) it.next();
+ if (ex.getType() == Extension.Type.SERVER_NAME)
+ {
+ if (extensions == null)
+ {
+ extensions = new LinkedList();
+ }
+ extensions.add(ex);
+ }
+ else if (ex.getType() == Extension.Type.MAX_FRAGMENT_LENGTH)
+ {
+ int maxLen = Extensions.getMaxFragmentLength(ex).intValue();
+// recordInput.setFragmentLength(maxLen);
+// recordOutput.setFragmentLength(maxLen);
+ session.params.setFragmentLength(maxLen);
+ if (extensions == null)
+ {
+ extensions = new LinkedList();
+ }
+ extensions.add(ex);
+ }
+ else if (ex.getType() == Extension.Type.SRP)
+ {
+ if (extensions == null)
+ {
+ extensions = new LinkedList();
+ }
+ byte[] b = ex.getValue();
+ remoteUser = new String(ex.getValue(), 1, b[0] & 0xFF, "UTF-8");
+ session.putValue("srp-username", remoteUser);
+ }
+ }
+ }
+
+ CipherSuite suite = selectSuite(clientHello.getCipherSuites(), version);
+ if (suite == null)
+ {
+ return;
+ }
+
+ // If the selected suite turns out to be SRP, set up the key exchange
+ // objects.
+ IKeyAgreementParty serverKA = null;
+ IncomingMessage in;
+ OutgoingMessage out = null;
+ if (suite.getKeyExchange() == "SRP")
+ {
+ // FIXME
+ // Uhm, I don't think this can happen, because if remoteUser is null
+ // we cannot choose an SRP ciphersuite...
+ if (remoteUser == null)
+ {
+ Alert alert = new Alert(Alert.Level.FATAL,
+ Alert.Description.MISSING_SRP_USERNAME);
+ sendAlert(alert);
+ throw new AlertException(alert, true);
+ }
+
+ SRPAuthInfoProvider srpDB = new SRPAuthInfoProvider();
+ Map dbAttributes = new HashMap();
+ dbAttributes.put(SRPRegistry.PASSWORD_DB,
+ session.srpTrustManager.getPasswordFile());
+ srpDB.activate(dbAttributes);
+
+ // FIXME
+ // We can also fake that the user exists, and generate a dummy (and
+ // invalid) master secret, and let the handshake fail at the Finished
+ // message. This is better than letting the connecting side know that
+ // the username they sent isn't valid.
+ //
+ // But how to implement this?
+ if (!srpDB.contains(remoteUser))
+ {
+ Alert alert = new Alert(Alert.Level.FATAL,
+ Alert.Description.UNKNOWN_SRP_USERNAME);
+ sendAlert(alert);
+ throw new AlertException(alert, true);
+ }
+
+ serverKA = KeyAgreementFactory.getPartyBInstance(Registry.SRP_TLS_KA);
+ Map serverAttributes = new HashMap();
+ serverAttributes.put(SRP6KeyAgreement.HASH_FUNCTION,
+ Registry.SHA160_HASH);
+ serverAttributes.put(SRP6KeyAgreement.HOST_PASSWORD_DB, srpDB);
+
+ try
+ {
+ serverKA.init(serverAttributes);
+ out = new OutgoingMessage();
+ out.writeString(remoteUser);
+ in = new IncomingMessage(out.toByteArray());
+ out = serverKA.processMessage(in);
+ }
+ catch (KeyAgreementException x)
+ {
+ throwHandshakeFailure();
+ }
+ }
+
+ // Check if the session specified by the client's ID corresponds
+ // to a saved session, and if so, continue it.
+ boolean newSession = true;
+ if (DEBUG_HANDSHAKE_LAYER)
+ {
+ logger.log (Component.SSL_HANDSHAKE, "saved sessions: {0}", sessionContext);
+ }
+ if (sessionContext.containsSessionID(
+ new Session.ID(clientHello.getSessionId())))
+ {
+ Session old = session;
+ session = (Session) sessionContext.getSession(clientHello.getSessionId());
+ if (!clientHello.getCipherSuites().contains(session.cipherSuite))
+ {
+ throwHandshakeFailure();
+ }
+ if (session.getPeerHost().equals(remoteHost) &&
+ old.enabledProtocols.contains(session.protocol))
+ {
+ session = (Session) session.clone();
+ suite = session.cipherSuite;
+ newSession = false;
+ recordInput.setSession(session);
+ session.currentAlert = null;
+ session.params = old.params;
+ session.random = old.random;
+ }
+ else
+ {
+ if (DEBUG_HANDSHAKE_LAYER)
+ {
+ logger.log (Component.SSL_HANDSHAKE, "rejected section; hosts equal? {0}, same suites? {1}",
+ new Object[] { Boolean.valueOf (session.getPeerHost().equals(remoteHost)),
+ Boolean.valueOf (old.enabledProtocols.contains(session.protocol)) });
+ }
+ session = old;
+ session.peerHost = remoteHost;
+ newSession = true;
+ }
+ }
+ else if (DEBUG_HANDSHAKE_LAYER)
+ {
+ logger.log (Component.SSL_HANDSHAKE, "rejected session; have session id? {0}, saved sessions: {1}",
+ new Object[] { Boolean.valueOf (sessionContext.containsSessionID(new Session.ID(clientHello.getSessionId()))),
+ sessionContext });
+ }
+ if (newSession)
+ {
+ byte[] buf = new byte[32];
+ Session.ID sid = null;
+ do
+ {
+ session.random.nextBytes(buf);
+ sid = new Session.ID(buf);
+ }
+ while (sessionContext.containsSessionID(sid));
+ session.sessionId = sid;
+ }
+ session.valid = true;
+ session.peerHost = remoteHost;
+ session.cipherSuite = suite;
+ session.protocol = version;
+ session.params.setVersion (version);
+
+ // Send the server hello.
+ Random serverRandom = new Random(Util.unixTime(),
+ session.random.generateSeed(28));
+ ServerHello serverHello = new ServerHello(version, serverRandom,
+ session.getId(), suite,
+ comp, extensions);
+ msg = new Handshake(Handshake.Type.SERVER_HELLO, serverHello);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ msg.write(dout, version);
+// recordOutput.setHandshakeAvail(msg.write(dout, version));
+ dout.flush();
+
+ if (newSession)
+ {
+ X509Certificate[] certs = null;
+ PrivateKey serverKey = null;
+ if (suite.getSignature() != "anon")
+ {
+ // Send our CA-issued certificate to the client.
+ String alias = session.keyManager.chooseServerAlias(suite.getAuthType(),
+ null, null);
+ certs = session.keyManager.getCertificateChain(alias);
+ serverKey = session.keyManager.getPrivateKey(alias);
+ if (certs == null || serverKey == null)
+ {
+ throwHandshakeFailure();
+ }
+ session.localCerts = certs;
+ Certificate serverCert = new Certificate(certs);
+ msg = new Handshake(Handshake.Type.CERTIFICATE, serverCert);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ msg.write(dout, version);
+// recordOutput.setHandshakeAvail(msg.write(dout, version));;
+ dout.flush();
+ }
+
+ // If the certificate we sent does not contain enough information to
+ // do the key exchange (in the case of ephemeral Diffie-Hellman,
+ // export RSA, and SRP) we send a signed public key to be used for the
+ // key exchange.
+ KeyPair signPair = null;
+ if (certs != null)
+ {
+ signPair = new KeyPair(certs[0].getPublicKey(), serverKey);
+ }
+ KeyPair kexPair = signPair;
+ ServerKeyExchange skex = null;
+
+ // Set up our key exchange, and/or prepare our ServerKeyExchange
+ // message.
+ if ((suite.getKeyExchange() == "RSA" && suite.isExportable() &&
+ ((RSAPrivateKey) serverKey).getModulus().bitLength() > 512))
+ {
+ kexPair = KeyPool.generateRSAKeyPair();
+ RSAPublicKey pubkey = (RSAPublicKey) kexPair.getPublic();
+ Signature s = null;
+ if (suite.getSignature() != "anon")
+ {
+ SSLRSASignature sig = new SSLRSASignature();
+ sig.setupSign(Collections.singletonMap(ISignature.SIGNER_KEY,
+ signPair.getPrivate()));
+ byte[] buf = clientRandom.getEncoded();
+ sig.update(buf, 0, buf.length);
+ buf = serverRandom.getEncoded();
+ sig.update(buf, 0, buf.length);
+ updateSig(sig, pubkey.getModulus());
+ updateSig(sig, pubkey.getPublicExponent());
+ s = new Signature(sig.sign(), "RSA");
+ }
+ skex = new ServerKeyExchange(pubkey, s);
+ }
+ else if (suite.getKeyExchange() == "DH")
+ {
+ serverKA = KeyAgreementFactory.getPartyBInstance(Registry.ELGAMAL_KA);
+ Map attr = new HashMap();
+ attr.put(ElGamalKeyAgreement.KA_ELGAMAL_RECIPIENT_PRIVATE_KEY,
+ serverKey);
+ try
+ {
+ serverKA.init(attr);
+ }
+ catch (KeyAgreementException kae)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ logger.log (Component.SSL_KEY_EXCHANGE, "DH exception", kae);
+ internalError();
+ RuntimeException re = new RuntimeException (kae.getMessage());
+ re.initCause (kae);
+ throw re;
+ }
+ // We don't send a ServerKeyExchange for this suite.
+ }
+ else if (suite.getKeyExchange() == "DHE")
+ {
+ serverKA = KeyAgreementFactory.getPartyAInstance(Registry.DH_KA);
+ Map attr = new HashMap();
+ GnuDHPrivateKey servParams = DiffieHellman.getParams();
+ attr.put(DiffieHellmanKeyAgreement.KA_DIFFIE_HELLMAN_OWNER_PRIVATE_KEY,
+ servParams);
+ attr.put(DiffieHellmanKeyAgreement.SOURCE_OF_RANDOMNESS,
+ session.random);
+ BigInteger serv_y = null;
+ try
+ {
+ serverKA.init(attr);
+ out = serverKA.processMessage(null);
+ in = new IncomingMessage(out.toByteArray());
+ serv_y = in.readMPI();
+ }
+ catch (KeyAgreementException kae)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "DHE exception", kae);
+ }
+ internalError();
+ RuntimeException re = new RuntimeException (kae.getMessage());
+ re.initCause (kae);
+ throw re;
+ }
+ GnuDHPublicKey pubkey =
+ new GnuDHPublicKey(null, servParams.getParams().getP(),
+ servParams.getParams().getG(), serv_y);
+ Signature s = null;
+ if (suite.getSignature() != "anon")
+ {
+ ISignature sig = null;
+ if (suite.getSignature() == "RSA")
+ {
+ sig = new SSLRSASignature();
+ }
+ else
+ {
+ sig = SignatureFactory.getInstance(Registry.DSS_SIG);
+ }
+ sig.setupSign(Collections.singletonMap(ISignature.SIGNER_KEY,
+ signPair.getPrivate()));
+ byte[] buf = clientRandom.getEncoded();
+ sig.update(buf, 0, buf.length);
+ buf = serverRandom.getEncoded();
+ sig.update(buf, 0, buf.length);
+ updateSig(sig, pubkey.getParams().getP());
+ updateSig(sig, pubkey.getParams().getG());
+ updateSig(sig, pubkey.getY());
+ s = new Signature(sig.sign(), suite.getSignature());
+ }
+ skex = new ServerKeyExchange(pubkey, s);
+ }
+ else if (suite.getKeyExchange() == "SRP")
+ {
+ BigInteger N = null;
+ BigInteger g = null;
+ BigInteger salt = null;
+ BigInteger B = null;
+ try
+ {
+ in = new IncomingMessage(out.toByteArray());
+ N = in.readMPI();
+ g = in.readMPI();
+ salt = in.readMPI();
+ B = in.readMPI();
+ }
+ catch (KeyAgreementException x)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "SRP exception", x);
+ }
+ throwHandshakeFailure();
+ }
+ Signature s = null;
+ final byte[] srpSalt = Util.trim(salt);
+ if (suite.getSignature() != "anon")
+ {
+ ISignature sig = null;
+ if (suite.getSignature() == "RSA")
+ {
+ sig = new SSLRSASignature();
+ }
+ else
+ {
+ sig = SignatureFactory.getInstance(Registry.DSS_SIG);
+ }
+ sig.setupSign(Collections.singletonMap(ISignature.SIGNER_KEY,
+ signPair.getPrivate()));
+ byte[] buf = clientRandom.getEncoded();
+ sig.update(buf, 0, buf.length);
+ buf = serverRandom.getEncoded();
+ sig.update(buf, 0, buf.length);
+ updateSig(sig, N);
+ updateSig(sig, g);
+ sig.update((byte) srpSalt.length);
+ sig.update(srpSalt, 0, srpSalt.length);
+ updateSig(sig, B);
+ s = new Signature(sig.sign(), suite.getSignature());
+ }
+ final SRPPublicKey pubkey = new SRPPublicKey(N, g, B);
+ skex = new ServerKeyExchange(pubkey, s, srpSalt);
+ }
+ if (skex != null)
+ {
+ msg = new Handshake(Handshake.Type.SERVER_KEY_EXCHANGE, skex);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ msg.write(dout, version);
+// recordOutput.setHandshakeAvail(msg.write(dout, version));;
+ dout.flush();
+ }
+
+ // If we are configured to want or need client authentication, then
+ // ask for it.
+ if (wantClientAuth || needClientAuth)
+ {
+ Principal[] auths = null;
+ CertificateRequest.ClientType[] types =
+ new CertificateRequest.ClientType[] {
+ CertificateRequest.ClientType.RSA_SIGN,
+ CertificateRequest.ClientType.DSS_SIGN,
+ CertificateRequest.ClientType.RSA_FIXED_DH,
+ CertificateRequest.ClientType.DSS_FIXED_DH
+ };
+ try
+ {
+ auths = (Principal[])
+ Util.transform(session.trustManager.getAcceptedIssuers(),
+ Principal.class, "getSubjectDN", null);
+ }
+ catch (Exception x)
+ {
+ internalError();
+ RuntimeException re = new RuntimeException (x.getMessage());
+ re.initCause (x);
+ throw re;
+ }
+ CertificateRequest req = new CertificateRequest(types, auths);
+ msg = new Handshake(Handshake.Type.CERTIFICATE_REQUEST, req);
+ msg.write(dout, version);
+ dout.flush();
+ }
+
+ // Send our server hello done.
+ msg = new Handshake(Handshake.Type.SERVER_HELLO_DONE, null);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ msg.write(dout, version);
+ dout.flush();
+
+ if (suite.getKeyExchange() == "RSA")
+ {
+ msg = Handshake.read(din, suite, kexPair.getPublic());
+ }
+ else
+ {
+ msg = Handshake.read(din, suite, null);
+ }
+ boolean clientCertOk = false;
+ boolean clientCanSign = false;
+ X509Certificate[] clientChain = null;
+ PublicKey clientKey = null;
+
+ // Read the client's certificate, if sent.
+ if (msg.getType() == Handshake.Type.CERTIFICATE)
+ {
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ Certificate cliCert = (Certificate) msg.getBody();
+ clientChain = cliCert.getCertificates();
+ try
+ {
+ session.trustManager.checkClientTrusted(clientChain,
+ suite.getAuthType());
+ session.peerCerts = clientChain;
+ session.peerVerified = true;
+ clientKey = clientChain[0].getPublicKey();
+ }
+ catch (Exception x)
+ {
+ }
+ clientCanSign = ((clientKey instanceof DSAPublicKey) ||
+ (clientKey instanceof RSAPublicKey));
+ if (suite.getKeyExchange().startsWith("DH"))
+ {
+ msg = Handshake.read(din, suite, clientKey);
+ }
+ else
+ {
+ msg = Handshake.read(din, suite, kexPair.getPublic());
+ }
+ }
+
+ // If we require client authentication, and the client sent an
+ // unverifiable certificate or no certificate at all, drop the
+ // connection.
+ if (!session.peerVerified && needClientAuth)
+ {
+ throwHandshakeFailure();
+ }
+
+ // Read the client key exchange.
+ if (msg.getType() != Handshake.Type.CLIENT_KEY_EXCHANGE)
+ {
+ throwUnexpectedMessage();
+ }
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ ClientKeyExchange ckex = (ClientKeyExchange) msg.getBody();
+ byte[] preMasterSecret = null;
+ if (suite.getKeyExchange() == "RSA")
+ {
+ byte[] enc = (byte[]) ckex.getExchangeObject();
+ BigInteger bi = new BigInteger(1, enc);
+ try
+ {
+ bi = RSA.decrypt(kexPair.getPrivate(), bi);
+ EME_PKCS1_V1_5 pkcs1 = EME_PKCS1_V1_5.getInstance(
+ (RSAPrivateKey) kexPair.getPrivate());
+ preMasterSecret = pkcs1.decode(Util.concat(new byte[1], bi.toByteArray()));
+ //rsa.init(kexPair);
+ //preMasterSecret = rsa.decrypt(enc);
+ }
+ catch (Exception x)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "RSA exception", x);
+ }
+ // Generate a fake pre-master secret if the RSA decryption
+ // fails.
+ byte[] b = new byte[46];
+ session.random.nextBytes (b);
+ preMasterSecret = Util.concat(version.getEncoded(), b);
+ }
+ }
+ else if (suite.getKeyExchange().startsWith("DH"))
+ {
+ try
+ {
+ out = new OutgoingMessage();
+ if (clientKey == null)
+ out.writeMPI((BigInteger) ckex.getExchangeObject());
+ else
+ out.writeMPI(((DHPublicKey) clientKey).getY());
+ in = new IncomingMessage(out.toByteArray());
+ serverKA.processMessage(in);
+ preMasterSecret = serverKA.getSharedSecret();
+ }
+ catch (KeyAgreementException kae)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "DH exception", kae);
+ }
+ internalError();
+ RuntimeException re = new RuntimeException (kae.getMessage());
+ re.initCause (kae);
+ throw re;
+ }
+ }
+ else if (suite.getKeyExchange() == "SRP")
+ {
+ BigInteger A = (BigInteger) ckex.getExchangeObject();
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "SRP: client A: {0}", A);
+ }
+ try
+ {
+ out = new OutgoingMessage();
+ out.writeMPI(A);
+ in = new IncomingMessage(out.toByteArray());
+ out = serverKA.processMessage(in);
+ preMasterSecret = serverKA.getSharedSecret();
+ }
+ catch (KeyAgreementException x)
+ {
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "SRP exception", x);
+ }
+ throwHandshakeFailure();
+ }
+ finally
+ {
+ serverKA = null;
+ }
+ }
+
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "preMasterSecret:\n{0}",
+ Util.toHexString(preMasterSecret, ':'));
+ logger.log (Component.SSL_KEY_EXCHANGE, "client.random:\n{0}",
+ Util.toHexString(clientRandom.getEncoded(), ':'));
+ logger.log (Component.SSL_KEY_EXCHANGE, "server.random:\n{0}",
+ Util.toHexString(serverRandom.getEncoded(), ':'));
+ }
+
+ // Generate the master secret.
+ IRandom genSecret = null;
+ if (version == ProtocolVersion.SSL_3)
+ {
+ genSecret = new SSLRandom();
+ HashMap attr = new HashMap();
+ attr.put(SSLRandom.SECRET, preMasterSecret);
+ attr.put(SSLRandom.SEED, Util.concat(clientRandom.getEncoded(),
+ serverRandom.getEncoded()));
+ genSecret.init(attr);
+ }
+ else
+ {
+ genSecret = new TLSRandom();
+ HashMap attr = new HashMap();
+ attr.put(TLSRandom.SECRET, preMasterSecret);
+ attr.put(TLSRandom.SEED,
+ Util.concat(("master secret").getBytes("UTF-8"),
+ Util.concat(clientRandom.getEncoded(),
+ serverRandom.getEncoded())));
+ genSecret.init(attr);
+ }
+ session.masterSecret = new byte[48];
+ try
+ {
+ genSecret.nextBytes(session.masterSecret, 0, 48);
+ for (int i = 0; i < preMasterSecret.length; i++)
+ {
+ preMasterSecret[i] = 0;
+ }
+ }
+ catch (LimitReachedException shouldNotHappen)
+ {
+ internalError();
+ RuntimeException re = new RuntimeException();
+ re.initCause (shouldNotHappen);
+ throw re;
+ }
+
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "masterSecret: {0}",
+ Util.toHexString(session.masterSecret, ':'));
+ }
+
+ // Read the client's certificate verify message, if needed.
+ if (clientCanSign && (wantClientAuth || needClientAuth))
+ {
+ msg = Handshake.read(din);
+ if (msg.getType() != Handshake.Type.CERTIFICATE_VERIFY)
+ {
+ throwUnexpectedMessage();
+ }
+ CertificateVerify verify = (CertificateVerify) msg.getBody();
+ if (clientChain != null && clientChain.length > 0)
+ {
+ IMessageDigest cvMD5 = (IMessageDigest) md5.clone();
+ IMessageDigest cvSHA = (IMessageDigest) sha.clone();
+ clientKey = clientChain[0].getPublicKey();
+ if (clientKey instanceof RSAPublicKey)
+ {
+ SSLRSASignature sig = new SSLRSASignature(cvMD5, cvSHA);
+ sig.setupVerify(Collections.singletonMap(ISignature.VERIFIER_KEY, clientKey));
+ if (!sig.verify(verify.getSigValue()))
+ {
+ handshakeFailure();
+ throw new SSLHandshakeException("client certificate verify failed");
+ }
+ }
+ else if (clientKey instanceof DSAPublicKey)
+ {
+ try
+ {
+ if (!DSSSignature.verify((DSAPublicKey) clientKey, cvSHA.digest(),
+ (BigInteger[]) verify.getSigValue()))
+ {
+ throw new Exception("client's certificate could not be verified");
+ }
+ }
+ catch (Exception x)
+ {
+ handshakeFailure();
+ SSLHandshakeException e = new SSLHandshakeException (x.getMessage());
+ e.initCause (x);
+ throw e;
+ }
+ }
+ }
+ }
+ }
+
+ // Generate the session keys.
+ byte[][] keys = null;
+ try
+ {
+ keys = generateKeys(serverRandom.getEncoded(),
+ clientRandom.getEncoded(), version);
+ }
+ catch (Exception x)
+ {
+ internalError();
+ RuntimeException re = new RuntimeException (x.getMessage());
+ re.initCause (x);
+ throw re;
+ }
+
+ // Initialize the algorithms with the derived keys.
+ Object readMac = null, writeMac = null;
+ Object readCipher = null, writeCipher = null;
+ try
+ {
+ if (session.params instanceof GNUSecurityParameters)
+ {
+ HashMap attr = new HashMap();
+ writeMac = CipherSuite.getMac(suite.getMac());
+ readMac = CipherSuite.getMac(suite.getMac());
+ attr.put(IMac.MAC_KEY_MATERIAL, keys[1]);
+ ((IMac) writeMac).init(attr);
+ attr.put(IMac.MAC_KEY_MATERIAL, keys[0]);
+ ((IMac) readMac).init(attr);
+ if (suite.getCipher() == "RC4")
+ {
+ writeCipher = new ARCFour();
+ readCipher = new ARCFour();
+ attr.clear();
+ attr.put(ARCFour.ARCFOUR_KEY_MATERIAL, keys[3]);
+ ((ARCFour) writeCipher).init(attr);
+ attr.put(ARCFour.ARCFOUR_KEY_MATERIAL, keys[2]);
+ ((ARCFour) readCipher).init(attr);
+ }
+ else if (!suite.isStreamCipher())
+ {
+ writeCipher = CipherSuite.getCipher(suite.getCipher());
+ readCipher = CipherSuite.getCipher(suite.getCipher());
+ attr.clear();
+ attr.put(IMode.KEY_MATERIAL, keys[3]);
+ attr.put(IMode.IV, keys[5]);
+ attr.put(IMode.STATE, new Integer(IMode.ENCRYPTION));
+ ((IMode) writeCipher).init(attr);
+ attr.put(IMode.KEY_MATERIAL, keys[2]);
+ attr.put(IMode.IV, keys[4]);
+ attr.put(IMode.STATE, new Integer(IMode.DECRYPTION));
+ ((IMode) readCipher).init(attr);
+ }
+ }
+ else // JCESecurityParameters
+ {
+ writeMac = CipherSuite.getJCEMac (suite.getMac());
+ readMac = CipherSuite.getJCEMac (suite.getMac());
+ writeCipher = CipherSuite.getJCECipher (suite.getCipher());
+ readCipher = CipherSuite.getJCECipher (suite.getCipher());
+ ((Mac) writeMac).init (new SecretKeySpec (keys[1], suite.getMac()));
+ ((Mac) readMac).init (new SecretKeySpec (keys[0], suite.getMac()));
+ if (!suite.isStreamCipher())
+ {
+ ((Cipher) writeCipher).init (Cipher.ENCRYPT_MODE,
+ new SecretKeySpec (keys[3], suite.getCipher()),
+ new IvParameterSpec (keys[5]));
+ ((Cipher) readCipher).init (Cipher.DECRYPT_MODE,
+ new SecretKeySpec (keys[2], suite.getCipher()),
+ new IvParameterSpec (keys[4]));
+ }
+ else
+ {
+ ((Cipher) writeCipher).init (Cipher.ENCRYPT_MODE,
+ new SecretKeySpec (keys[3], suite.getCipher()));
+ ((Cipher) readCipher).init (Cipher.DECRYPT_MODE,
+ new SecretKeySpec (keys[2], suite.getCipher()));
+ }
+ }
+ }
+ // These should technically never happen, if our key generation is not
+ // broken.
+ catch (InvalidKeyException ike)
+ {
+ internalError();
+ RuntimeException re = new RuntimeException (ike.getMessage());
+ re.initCause (ike);
+ throw new RuntimeException (String.valueOf (ike));
+ }
+ catch (InvalidAlgorithmParameterException iape)
+ {
+ internalError();
+ RuntimeException re = new RuntimeException (iape.getMessage());
+ re.initCause (iape);
+ throw re;
+ }
+ // These indicate a configuration error with the JCA.
+ catch (NoSuchAlgorithmException nsae)
+ {
+ session.enabledSuites.remove (suite);
+ internalError();
+ SSLException e = new SSLException ("suite " + suite + " not available in this configuration");
+ e.initCause (nsae);
+ throw e;
+ }
+ catch (NoSuchPaddingException nspe)
+ {
+ session.enabledSuites.remove (suite);
+ internalError();
+ SSLException e = new SSLException ("suite " + suite + " not available in this configuration");
+ e.initCause (nspe);
+ throw e;
+ }
+
+ Finished finis = null;
+ // If we are continuing a session, we send our Finished message first.
+ if (!newSession)
+ {
+ changeCipherSpec();
+ session.params.setDeflating(comp == CompressionMethod.ZLIB);
+ session.params.setOutMac(writeMac);
+ session.params.setOutCipher(writeCipher);
+ finis = generateFinished(version, (IMessageDigest) md5.clone(),
+ (IMessageDigest) sha.clone(), false);
+ msg = new Handshake(Handshake.Type.FINISHED, finis);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ msg.write(dout, version);
+ dout.flush();
+ }
+
+ if (session.currentAlert != null &&
+ session.currentAlert.getLevel() == Alert.Level.FATAL)
+ {
+ fatal();
+ throw new AlertException(session.currentAlert, false);
+ }
+
+ // Wait until we receive a ChangeCipherSpec, then change the crypto
+ // algorithms for the incoming side.
+ synchronized (session.params)
+ {
+ readChangeCipherSpec ();
+ session.params.setInflating(comp == CompressionMethod.ZLIB);
+ session.params.setInMac(readMac);
+ session.params.setInCipher(readCipher);
+ session.params.notifyAll();
+ }
+
+ // Receive and verify the client's finished message.
+ Finished verify = generateFinished(version, (IMessageDigest) md5.clone(),
+ (IMessageDigest) sha.clone(), true);
+ msg = Handshake.read(din, suite, null);
+ if (msg.getType() != Handshake.Type.FINISHED)
+ {
+ throwUnexpectedMessage();
+ }
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ finis = (Finished) msg.getBody();
+ if (version == ProtocolVersion.SSL_3)
+ {
+ if (!Arrays.equals(finis.getMD5Hash(), verify.getMD5Hash()) ||
+ !Arrays.equals(finis.getSHAHash(), verify.getSHAHash()))
+ {
+ throwHandshakeFailure();
+ }
+ }
+ else
+ {
+ if (!Arrays.equals(finis.getVerifyData(), verify.getVerifyData()))
+ {
+ throwHandshakeFailure();
+ }
+ }
+
+ // Send our Finished message last for new sessions.
+ if (newSession)
+ {
+ changeCipherSpec();
+ session.params.setDeflating(comp == CompressionMethod.ZLIB);
+ session.params.setOutMac(writeMac);
+ session.params.setOutCipher(writeCipher);
+ finis = generateFinished(version, md5, sha, false);
+ msg = new Handshake(Handshake.Type.FINISHED, finis);
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}", msg);
+ msg.write(dout, version);
+ dout.flush();
+ }
+
+ handshakeCompleted();
+ }
+
+ /**
+ * Generate the keys from the master secret.
+ *
+ * @param server The server's random value.
+ * @param client The client's random value.
+ * @param activeVersion The negotiated protocol version.
+ * @return The generated keys.
+ */
+ private byte[][] generateKeys(byte[] server, byte[] client,
+ ProtocolVersion activeVersion)
+ throws LimitReachedException, IOException
+ {
+ CipherSuite suite = session.cipherSuite;
+ int macLen = (suite.getMac().indexOf("MD5") >= 0) ? 16 : 20;
+ int keyLen = suite.getKeyLength();
+ int ivLen = 0;
+ if (suite.getCipher().indexOf("DES") >= 0)
+ {
+ ivLen = 8;
+ }
+ else if (suite.getCipher() == "AES")
+ {
+ ivLen = 16;
+ }
+ byte[][] keyMaterial = new byte[6][];
+ keyMaterial[0] = new byte[macLen]; // client_write_MAC_secret
+ keyMaterial[1] = new byte[macLen]; // server_write_MAC_secret
+ keyMaterial[2] = new byte[keyLen]; // client_write_key
+ keyMaterial[3] = new byte[keyLen]; // server_write_key
+ keyMaterial[4] = new byte[ivLen]; // client_write_IV
+ keyMaterial[5] = new byte[ivLen]; // server_write_IV
+ IRandom prf = null;
+ if (activeVersion == ProtocolVersion.SSL_3)
+ {
+ prf = new SSLRandom();
+ HashMap attr = new HashMap();
+ attr.put(SSLRandom.SECRET, session.masterSecret);
+ attr.put(SSLRandom.SEED, Util.concat(server, client));
+ prf.init(attr);
+ }
+ else
+ {
+ prf = new TLSRandom();
+ HashMap attr = new HashMap();
+ attr.put(TLSRandom.SECRET, session.masterSecret);
+ attr.put(TLSRandom.SEED, Util.concat("key expansion".getBytes("UTF-8"),
+ Util.concat(server, client)));
+ prf.init(attr);
+ }
+ for (int i = 0; i < keyMaterial.length; i++)
+ {
+ prf.nextBytes(keyMaterial[i], 0, keyMaterial[i].length);
+ }
+
+ // Exportable ciphers transform their keys once more, and use a
+ // nonsecret IV for block ciphers.
+ if (suite.isExportable())
+ {
+ int finalLen = suite.getCipher() == "DES" ? 8 : 16;
+ if (activeVersion == ProtocolVersion.SSL_3)
+ {
+ IMessageDigest md5 = HashFactory.getInstance(Registry.MD5_HASH);
+ md5.update(keyMaterial[2], 0, keyMaterial[2].length);
+ md5.update(client, 0, client.length);
+ md5.update(server, 0, server.length);
+ keyMaterial[2] = Util.trim(md5.digest(), finalLen);
+ md5.update(keyMaterial[3], 0, keyMaterial[3].length);
+ md5.update(server, 0, server.length);
+ md5.update(client, 0, client.length);
+ keyMaterial[3] = Util.trim(md5.digest(), finalLen);
+ if (!suite.isStreamCipher())
+ {
+ md5.update(client, 0, client.length);
+ md5.update(server, 0, server.length);
+ keyMaterial[4] = Util.trim(md5.digest(), ivLen);
+ md5.update(server, 0, server.length);
+ md5.update(client, 0, client.length);
+ keyMaterial[5] = Util.trim(md5.digest(), ivLen);
+ }
+ }
+ else
+ {
+ HashMap attr = new HashMap();
+ attr.put(TLSRandom.SECRET, keyMaterial[2]);
+ attr.put(TLSRandom.SEED,
+ Util.concat("client write key".getBytes("UTF-8"),
+ Util.concat(client, server)));
+ prf.init(attr);
+ keyMaterial[2] = new byte[finalLen];
+ prf.nextBytes(keyMaterial[2], 0, finalLen);
+ attr.put(TLSRandom.SECRET, keyMaterial[3]);
+ attr.put(TLSRandom.SEED,
+ Util.concat("server write key".getBytes("UTF-8"),
+ Util.concat(client, server)));
+ prf.init(attr);
+ keyMaterial[3] = new byte[finalLen];
+ prf.nextBytes(keyMaterial[3], 0, finalLen);
+ if (!suite.isStreamCipher())
+ {
+ attr.put(TLSRandom.SECRET, new byte[0]);
+ attr.put(TLSRandom.SEED, Util.concat("IV block".getBytes("UTF-8"),
+ Util.concat(client, server)));
+ prf.init(attr);
+ prf.nextBytes(keyMaterial[4], 0, keyMaterial[4].length);
+ prf.nextBytes(keyMaterial[5], 0, keyMaterial[5].length);
+ }
+ }
+ }
+
+ if (DEBUG_KEY_EXCHANGE)
+ {
+ logger.log (Component.SSL_KEY_EXCHANGE, "Generated keys:");
+ for (int i = 0; i < keyMaterial.length; i++)
+ logger.log (Component.SSL_KEY_EXCHANGE, "[{0}] {1}",
+ new Object[] { new Integer (i),
+ Util.toHexString(keyMaterial[i], ':') });
+ }
+
+ return keyMaterial;
+ }
+
+ /**
+ * Generate a "finished" message, based on the hashes of the handshake
+ * messages, the agreed version, and a label.
+ *
+ * @param version The agreed version.
+ * @param md5 The current state of the handshake MD5 hash.
+ * @param sha The current state of the handshake SHA hash.
+ * @param client Should be true if the message is generated by the client.
+ */
+ private Finished generateFinished(ProtocolVersion version, IMessageDigest md5,
+ IMessageDigest sha, boolean client)
+ {
+ if (version == ProtocolVersion.SSL_3)
+ {
+ if (client)
+ {
+ md5.update(SENDER_CLIENT, 0, 4);
+ }
+ else
+ {
+ md5.update(SENDER_SERVER, 0, 4);
+ }
+ byte[] ms = session.masterSecret;
+ md5.update(ms, 0, ms.length);
+ for (int i = 0; i < 48; i++)
+ {
+ md5.update(SSLHMac.PAD1);
+ }
+ byte[] b = md5.digest();
+ md5.update(ms, 0, ms.length);
+ for (int i = 0; i < 48; i++)
+ {
+ md5.update(SSLHMac.PAD2);
+ }
+ md5.update(b, 0, b.length);
+
+ if (client)
+ {
+ sha.update(SENDER_CLIENT, 0, 4);
+ }
+ else
+ {
+ sha.update(SENDER_SERVER, 0, 4);
+ }
+ sha.update(ms, 0, ms.length);
+ for (int i = 0; i < 40; i++)
+ {
+ sha.update(SSLHMac.PAD1);
+ }
+ b = sha.digest();
+ sha.update(ms, 0, ms.length);
+ for (int i = 0; i < 40; i++)
+ {
+ sha.update(SSLHMac.PAD2);
+ }
+ sha.update(b, 0, b.length);
+ return new Finished(md5.digest(), sha.digest());
+ }
+ else
+ {
+ byte[] h1 = md5.digest();
+ byte[] h2 = sha.digest();
+ String label = client ? "client finished" : "server finished";
+ byte[] seed = null;
+ try
+ {
+ seed = Util.concat(label.getBytes("UTF-8"), Util.concat(h1, h2));
+ }
+ catch (java.io.UnsupportedEncodingException uee)
+ {
+ RuntimeException re = new RuntimeException (uee.getMessage());
+ re.initCause (uee);
+ throw re;
+ }
+ IRandom prf = new TLSRandom();
+ HashMap attr = new HashMap();
+ attr.put(TLSRandom.SECRET, session.masterSecret);
+ attr.put(TLSRandom.SEED, seed);
+ prf.init(attr);
+ byte[] finishedValue = new byte[12];
+ try
+ {
+ prf.nextBytes(finishedValue, 0, 12);
+ }
+ catch (LimitReachedException lre)
+ {
+ RuntimeException re = new RuntimeException (lre.getMessage());
+ re.initCause (lre);
+ throw re;
+ }
+ return new Finished(finishedValue);
+ }
+ }
+
+ /**
+ * Send a fatal unexpected_message alert.
+ */
+ private Alert unexpectedMessage() throws IOException
+ {
+ Alert alert = new Alert(Alert.Level.FATAL,
+ Alert.Description.UNEXPECTED_MESSAGE);
+ sendAlert(alert);
+ fatal();
+ return alert;
+ }
+
+ private void throwUnexpectedMessage() throws IOException
+ {
+ throw new AlertException(unexpectedMessage(), true);
+ }
+
+ /**
+ * Send a fatal handshake_failure alert.
+ */
+ private Alert handshakeFailure() throws IOException
+ {
+ Alert alert = new Alert(Alert.Level.FATAL,
+ Alert.Description.HANDSHAKE_FAILURE);
+ sendAlert(alert);
+ fatal();
+ return alert;
+ }
+
+ private void throwHandshakeFailure() throws IOException
+ {
+ throw new AlertException(handshakeFailure(), true);
+ }
+
+ /**
+ * Send an internal_error alert.
+ */
+ private Alert internalError() throws IOException
+ {
+ Alert alert = new Alert(Alert.Level.FATAL,
+ Alert.Description.INTERNAL_ERROR);
+ sendAlert(alert);
+ fatal();
+ return alert;
+ }
+
+ private void throwInternalError() throws IOException
+ {
+ throw new AlertException(internalError(), true);
+ }
+
+ private Alert peerUnverified(X509Certificate[] chain) throws IOException
+ {
+ Alert alert = new Alert(Alert.Level.FATAL,
+ Alert.Description.HANDSHAKE_FAILURE);
+ sendAlert(alert);
+ fatal();
+ return alert;
+ }
+
+ private void throwPeerUnverified(X509Certificate[] chain) throws IOException
+ {
+ peerUnverified (chain);
+ throw new SSLPeerUnverifiedException("could not verify: "+
+ chain[0].getSubjectDN());
+ }
+
+ /**
+ * Grab the first suite that is both in the client's requested suites
+ * and in our enabled suites, and for which we have the proper
+ * credentials.
+ *
+ * @param suites The client's requested suites.
+ * @param version The version being negotiated.
+ * @return The selected cipher suite.
+ * @throws SSLException If no appropriate suite can be selected.
+ */
+ private CipherSuite selectSuite(List suites, ProtocolVersion version)
+ throws IOException
+ {
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "selectSuite req:{0} suites:{1}",
+ new Object[] { suites, session.enabledSuites });
+ boolean srpSuiteNoUser = false;
+ for (Iterator i = suites.iterator(); i.hasNext(); )
+ {
+ CipherSuite herSuite = (CipherSuite) i.next();
+ for (Iterator j = session.enabledSuites.iterator(); j.hasNext(); )
+ {
+ CipherSuite mySuite = (CipherSuite) j.next();
+ if (!mySuite.equals(herSuite))
+ {
+ continue;
+ }
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0} == {1}",
+ new Object[] { mySuite, herSuite });
+ if (mySuite.getSignature() != "anon" && session.keyManager != null &&
+ session.keyManager.chooseServerAlias(mySuite.getAuthType(), null, null) == null)
+ {
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "{0}: no certificate/private key",
+ mySuite);
+ continue;
+ }
+ if (mySuite.getKeyExchange() == "SRP")
+ {
+ if (session.getValue("srp-username") == null)
+ {
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "no SRP username");
+ srpSuiteNoUser = true;
+ continue;
+ }
+ if (session.srpTrustManager == null)
+ {
+ if (DEBUG_HANDSHAKE_LAYER)
+ logger.log (Component.SSL_HANDSHAKE, "no SRP password file");
+ continue;
+ }
+ }
+ return mySuite.resolve(version);
+ }
+ }
+ Alert alert = null;
+ if (srpSuiteNoUser)
+ {
+ alert = new Alert(Alert.Level.WARNING,
+ Alert.Description.MISSING_SRP_USERNAME);
+ sendAlert(alert);
+ return null;
+ }
+ else
+ alert = new Alert(Alert.Level.FATAL,
+ Alert.Description.INSUFFICIENT_SECURITY);
+ sendAlert(alert);
+ fatal();
+ throw new AlertException(alert, true);
+ }
+
+ /**
+ * Ask the user for their user name.
+ *
+ * @param remoteHost The remote host being connected to.
+ * @return The user name.
+ */
+ private String askUserName(String remoteHost)
+ {
+ CallbackHandler handler = new DefaultCallbackHandler();
+ try
+ {
+ Class c = Class.forName(Util.getSecurityProperty("jessie.srp.user.handler"));
+ handler = (CallbackHandler) c.newInstance();
+ }
+ catch (Exception x) { }
+ TextInputCallback user =
+ new TextInputCallback("User name for " + remoteHost + ": ",
+ Util.getProperty("user.name"));
+ try
+ {
+ handler.handle(new Callback[] { user });
+ }
+ catch (Exception x) { }
+ return user.getText();
+ }
+
+ /**
+ * Ask the user for a password.
+ *
+ * @param user The user name.
+ * @return The password.
+ */
+ private String askPassword(String user)
+ {
+ CallbackHandler handler = new DefaultCallbackHandler();
+ try
+ {
+ Class c = Class.forName(Util.getSecurityProperty("jessie.srp.password.handler"));
+ handler = (CallbackHandler) c.newInstance();
+ }
+ catch (Exception x) { }
+ PasswordCallback passwd = new PasswordCallback(user + "'s password: ", false);
+ try
+ {
+ handler.handle(new Callback[] { passwd });
+ }
+ catch (Exception x) { }
+ return new String(passwd.getPassword());
+ }
+
+ /**
+ * Ask the user (via a callback) if they will accept a certificate that
+ * could not be verified.
+ *
+ * @param chain The certificate chain in question.
+ * @return true if the user accepts the certificate chain.
+ */
+ private boolean checkCertificates(X509Certificate[] chain)
+ {
+ CallbackHandler handler = new DefaultCallbackHandler();
+ try
+ {
+ Class c = Class.forName(Util.getSecurityProperty("jessie.certificate.handler"));
+ handler = (CallbackHandler) c.newInstance();
+ }
+ catch (Exception x)
+ {
+ }
+ String nl = Util.getProperty("line.separator");
+ ConfirmationCallback confirm = new ConfirmationCallback(
+ "The server's certificate could not be verified. There is no proof" + nl +
+ "that this server is who it claims to be, or that their certificate" + nl +
+ "is valid. Do you wish to continue connecting?",
+ ConfirmationCallback.ERROR, ConfirmationCallback.YES_NO_OPTION,
+ ConfirmationCallback.NO);
+ try
+ {
+ handler.handle(new Callback[] { confirm });
+ }
+ catch (Exception x)
+ {
+ return false;
+ }
+ return confirm.getSelectedIndex() == ConfirmationCallback.YES;
+ }
+
+ /**
+ * Update a signature object with a BigInteger, trimming the leading
+ * "00" octet if present.
+ *
+ * @param sig The signature being updated.
+ * @param bi The integer to feed into the signature.
+ */
+ private void updateSig(ISignature sig, BigInteger bi)
+ {
+ byte[] buf = Util.trim(bi);
+ sig.update((byte) (buf.length >>> 8));
+ sig.update((byte) buf.length);
+ sig.update(buf, 0, buf.length);
+ }
+
+ /**
+ * Teardown everything on fatal errors.
+ */
+ private void fatal() throws IOException
+ {
+ if (session != null)
+ {
+ session.invalidate();
+ }
+// recordInput.setRunning(false);
+// recordOutput.setRunning(false);
+ if (underlyingSocket != null)
+ {
+ underlyingSocket.close();
+ }
+ else
+ {
+ super.close();
+ }
+ }
+}
OpenPOWER on IntegriCloud