Logo Search packages:      
Sourcecode: kaffe version File versions  Download package

SSLSocket.java

/* SSLSocket.java -- the SSL socket class.
   Copyright (C) 2003,2004  Casey Marshall <rsdio@metastatic.org>

This file is a part of Jessie.

Jessie 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.

Jessie 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 Jessie; if not, write to the

   Free Software Foundation, Inc.,
   51 Franklin Street, 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 org.metastatic.jessie.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 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.crypto.Registry;
import gnu.crypto.auth.callback.DefaultCallbackHandler;
import gnu.crypto.hash.HashFactory;
import gnu.crypto.hash.IMessageDigest;
import gnu.crypto.key.IKeyAgreementParty;
import gnu.crypto.key.KeyAgreementFactory;
import gnu.crypto.key.KeyAgreementException;
import gnu.crypto.key.OutgoingMessage;
import gnu.crypto.key.IncomingMessage;
import gnu.crypto.key.dh.DiffieHellmanKeyAgreement;
import gnu.crypto.key.dh.ElGamalKeyAgreement;
import gnu.crypto.key.dh.GnuDHPrivateKey;
import gnu.crypto.key.dh.GnuDHPublicKey;
import gnu.crypto.key.srp6.SRPPrivateKey;
import gnu.crypto.key.srp6.SRPPublicKey;
import gnu.crypto.key.srp6.SRP6KeyAgreement;
import gnu.crypto.mac.IMac;
import gnu.crypto.mode.IMode;
import gnu.crypto.prng.ARCFour;
import gnu.crypto.prng.IRandom;
import gnu.crypto.prng.LimitReachedException;
import gnu.crypto.sasl.srp.SRPAuthInfoProvider;
import gnu.crypto.sasl.srp.SRPRegistry;
import gnu.crypto.sig.ISignature;
import gnu.crypto.sig.SignatureFactory;
import gnu.crypto.sig.dss.DSSSignature;
import gnu.crypto.sig.rsa.EME_PKCS1_V1_5;
import gnu.crypto.sig.rsa.RSA;

import org.metastatic.jessie.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.
 */
00146 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 = false;
  private static final boolean DEBUG_KEY_EXCHANGE = false;
  private static final PrintStream debug = System.err;

  // 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.
  // -------------------------------------------------------------------------

00321   public void addHandshakeCompletedListener(HandshakeCompletedListener l)
  {
    synchronized (handshakeListeners)
      {
        if (l == null)
          throw new NullPointerException();
        if (!handshakeListeners.contains(l))
          handshakeListeners.add(l);
      }
  }

00332   public void removeHandshakeCompletedListener(HandshakeCompletedListener l)
  {
    synchronized (handshakeListeners)
      {
        handshakeListeners.remove(l);
      }
  }

00340   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;
          }
      }
  }

00358   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);
              }
          }
      }
  }

00394   public String[] getSupportedProtocols()
  {
    return new String[] { /* "TLSv1.1", */ "TLSv1", "SSLv3" };
  }

00399   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;
          }
      }
  }

00417   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);
              }
          }
      }
  }

00439   public String[] getSupportedCipherSuites()
  {
    return (String[]) CipherSuite.availableSuiteNames().toArray(new String[52]);
  }

00444   public SSLSession getSession()
  {
    return session;
  }

00449   public boolean getEnableSessionCreation()
  {
    return createSessions;
  }

00454   public void setEnableSessionCreation(boolean flag)
  {
    createSessions = flag;
  }

00459   public boolean getNeedClientAuth()
  {
    return needClientAuth;
  }

00464   public void setNeedClientAuth(boolean flag)
  {
    needClientAuth = flag;
  }

00469   public boolean getWantClientAuth()
  {
    return wantClientAuth;
  }

00474   public void setWantClientAuth(boolean flag)
  {
    wantClientAuth = flag;
  }

00479   public boolean getUseClientMode()
  {
    return clientMode;
  }

00484   public void setUseClientMode(boolean flag)
  {
    this.clientMode = flag;
  }

00489   public synchronized void startHandshake() throws IOException
  {
    if (DEBUG_HANDSHAKE_LAYER)
      {
        debug.println("startHandshake called in " +
                      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.
  // -------------------------------------------------------------------------

00530   public InetAddress getInetAddress()
  {
    if (underlyingSocket != null)
      {
        return underlyingSocket.getInetAddress();
      }
    else
      {
        return super.getInetAddress();
      }
  }

00542   public InetAddress getLocalAddress()
  {
    if (underlyingSocket != null)
      {
        return underlyingSocket.getLocalAddress();
      }
    else
      {
        return super.getLocalAddress();
      }
  }

00554   public int getPort()
  {
    if (underlyingSocket != null)
      {
        return underlyingSocket.getPort();
      }
    else
      {
        return super.getPort();
      }
  }

00566   public int getLocalPort()
  {
    if (underlyingSocket != null)
      {
        return underlyingSocket.getLocalPort();
      }
    else
      {
        return super.getLocalPort();
      }
  }

00578   public InputStream getInputStream() throws IOException
  {
    if (applicationIn == null)
      {
        setupIO();
      }
    return applicationIn;
  }

00587   public OutputStream getOutputStream() throws IOException
  {
    if (applicationOut == null)
      {
        setupIO();
      }
    return applicationOut;
  }

00596   public void setTcpNoDelay(boolean flag) throws SocketException
  {
    if (underlyingSocket != null)
      {
        underlyingSocket.setTcpNoDelay(flag);
      }
    else
      {
        super.setTcpNoDelay(flag);
      }
  }

00608   public boolean getTcpNoDelay() throws SocketException
  {
    if (underlyingSocket != null)
      {
        return underlyingSocket.getTcpNoDelay();
      }
    else
      {
        return super.getTcpNoDelay();
      }
  }

00620   public void setSoLinger(boolean flag, int linger) throws SocketException
  {
    if (underlyingSocket != null)
      {
        underlyingSocket.setSoLinger(flag, linger);
      }
    else
      {
        super.setSoLinger(flag, linger);
      }
  }

00632   public int getSoLinger() throws SocketException
  {
    if (underlyingSocket != null)
      {
        return underlyingSocket.getSoLinger();
      }
    else
      {
        return super.getSoLinger();
      }
  }

00644   public void sendUrgentData(int data) throws IOException
  {
    throw new UnsupportedOperationException("not implemented");
  }

00649   public void setSoTimeout(int timeout) throws SocketException
  {
    if (underlyingSocket != null)
      {
        underlyingSocket.setSoTimeout(timeout);
      }
    else
      {
        super.setSoTimeout(timeout);
      }
  }

00661   public int getSoTimeout() throws SocketException
  {
    if (underlyingSocket != null)
      {
        return underlyingSocket.getSoTimeout();
      }
    else
      {
        return super.getSoTimeout();
      }
  }

00673   public void setSendBufferSize(int size) throws SocketException
  {
    if (underlyingSocket != null)
      {
        underlyingSocket.setSendBufferSize(size);
      }
    else
      {
        super.setSendBufferSize(size);
      }
  }

00685   public int getSendBufferSize() throws SocketException
  {
    if (underlyingSocket != null)
      {
        return underlyingSocket.getSendBufferSize();
      }
    else
      {
        return super.getSendBufferSize();
      }
  }

00697   public void setReceiveBufferSize(int size) throws SocketException
  {
    if (underlyingSocket != null)
      {
        underlyingSocket.setReceiveBufferSize(size);
      }
    else
      {
        super.setReceiveBufferSize(size);
      }
  }

00709   public int getReceiveBufferSize() throws SocketException
  {
    if (underlyingSocket != null)
      {
        return underlyingSocket.getReceiveBufferSize();
      }
    else
      {
        return super.getReceiveBufferSize();
      }
  }

00721   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");
      }
  }

00769   public String toString()
  {
    if (underlyingSocket != null)
      {
        return SSLSocket.class.getName() + " [ " + underlyingSocket + " ]";
      }
    else
      {
        return SSLSocket.class.getName() + " [ " + super.toString() + " ]";
      }
  }

  // Configuration insanity begins here.

00783   public void connect(SocketAddress saddr) throws IOException
  {
    if (underlyingSocket != null)
      {
        underlyingSocket.connect(saddr);
      }
    else
      {
        super.connect(saddr);
      }
  }

00795   public void connect(SocketAddress saddr, int timeout) throws IOException
  {
    if (underlyingSocket != null)
      {
        underlyingSocket.connect(saddr, timeout);
      }
    else
      {
        super.connect(saddr, timeout);
      }
  }

00807   public void bind(SocketAddress saddr) throws IOException
  {
    if (underlyingSocket != null)
      {
        underlyingSocket.bind(saddr);
      }
    else
      {
        super.bind(saddr);
      }
  }

00819   public SocketAddress getLocalSocketAddress()
  {
    if (underlyingSocket != null)
      {
        return underlyingSocket.getLocalSocketAddress();
      }
    else
      {
        return super.getLocalSocketAddress();
      }
  }

00831   public SocketChannel getChannel()
  {
    return channel;
  }

00836   public boolean isBound()
  {
    if (underlyingSocket != null)
      {
        return underlyingSocket.isBound();
      }
    else
      {
        return super.isBound();
      }
    //throw new UnsupportedOperationException("1.4 methods not enabled");
  }

00849   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();
  //    }
  //}

00874   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");
  }

00887   public boolean getOOBInline() throws SocketException
  {
    //if (underlyingSocket != null)
    //  {
    //    return underlyingSocket.getOOBInline();
    //  }
    //else
    //  {
    //    return super.getOOBInline();
    //  }
    throw new UnsupportedOperationException("1.4 methods not enabled");
  }

00900   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");
  }

00913   public boolean getKeepAlive() throws SocketException
  {
    //if (underlyingSocket != null)
    //  {
    //    return underlyingSocket.getKeepAlive();
    //  }
    //else
    //  {
    //    return super.getKeepAlive();
    //  }
    throw new UnsupportedOperationException("1.4 methods not enabled");
  }

00926   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");
  }

00939   public int getTrafficClass() throws SocketException
  {
    //if (underlyingSocket != null)
    //  {
    //    return underlyingSocket.getTrafficClass();
    //  }
    //else
    //  {
    //    return super.getTrafficClass();
    //  }
    throw new UnsupportedOperationException("1.4 methods not enabled");
  }

00952   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");
  }

00965   public boolean getReuseAddress() throws SocketException
  {
    //if (underlyingSocket != null)
    //  {
    //    return underlyingSocket.getReuseAddress();
    //  }
    //else
    //  {
    //    return super.getReuseAddress();
    //  }
    throw new UnsupportedOperationException("1.4 methods not enabled");
  }

00978   public void shutdownInput() throws IOException
  {
    //if (underlyingSocket != null)
    //  {
    //    underlyingSocket.shutdownInput();
    //  }
    //else
    //  {
    //    super.shutdownInput();
    //  }
    throw new UnsupportedOperationException("1.4 methods not enabled");
  }

00991   public void shutdownOutput() throws IOException
  {
    //if (underlyingSocket != null)
    //  {
    //    underlyingSocket.shutdownOutput();
    //  }
    //else
    //  {
    //    super.shutdownOutput();
    //  }
    throw new UnsupportedOperationException("1.4 methods not enabled");
  }

01004   public boolean isConnected()
  {
    if (underlyingSocket != null)
      {
        return underlyingSocket.isConnected();
      }
    else
      {
        return super.isConnected();
      }
    //throw new UnsupportedOperationException("1.4 methods not enabled");
  }

01017   public boolean isInputShutdown()
  {
    //if (underlyingSocket != null)
    //  {
    //    return underlyingSocket.isInputShutdown();
    //  }
    //else
    //  {
    //    return super.isInputShutdown();
    //  }
    throw new UnsupportedOperationException("1.4 methods not enabled");
  }

01030   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.
   */
01105   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.
   */
01157   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)
      {
        debug.println("Handshake finished in " + Thread.currentThread());
        handshakeTime = System.currentTimeMillis() - handshakeTime;
        debug.println("Elapsed time " + (handshakeTime / 1000) + "s");
      }
  }

  /*
   * 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)
      {
        debug.println("starting client handshake in " +
                      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) debug.println(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) debug.println(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;
        session = (Session) continuedSession.clone();
        session.params = params;
        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)
              {
                debug.println("password read is '"+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)
                  {
                    x.printStackTrace(debug);
                  }
                throwHandshakeFailure();
              }
          }

        if (suite.getSignature() != "anon")
          {
            msg = Handshake.read(din, certType);
            if (DEBUG_HANDSHAKE_LAYER) debug.println(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) debug.println(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)
                      {
                        debug.println(clientKA.isComplete());
                      }
                  }
                catch (KeyAgreementException x)
                  {
                    if (DEBUG_KEY_EXCHANGE)
                      {
                        x.printStackTrace(debug);
                      }
                    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) debug.println(msg);
            certReq = (CertificateRequest) msg.getBody();
            msg = Handshake.read(din);
          }

        // Read ServerHelloDone.
        if (msg.getType() != Handshake.Type.SERVER_HELLO_DONE)
          {
            throwUnexpectedMessage();
          }
        if (DEBUG_HANDSHAKE_LAYER) debug.println(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) debug.println(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();
            preMasterSecret =
              Util.concat(v.getEncoded(), new byte[46]);
            try
              {
                CSPRNG.SYSTEM_RANDOM.nextBytes (preMasterSecret,
                  preMasterSecret.length - 46, 46);
              }
            catch (LimitReachedException lre)
              {
                internalError();
                RuntimeException re = new RuntimeException (lre.getMessage());
                re.initCause (lre);
                throw re;
              }
            EME_PKCS1_V1_5 pkcs1 = EME_PKCS1_V1_5.getInstance((RSAPublicKey) serverKex);
            BigInteger bi = new BigInteger(1,
              pkcs1.encode(preMasterSecret, CSPRNG.SYSTEM_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,
                         CSPRNG.SYSTEM_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)
                      {
                        kae.printStackTrace(debug);
                      }
                    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)
                      kae.printStackTrace(debug);
                    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)
                  {
                    debug.println("client A=" + A);
                  }
              }
            catch (KeyAgreementException x)
              {
                if (DEBUG_KEY_EXCHANGE)
                  {
                    x.printStackTrace(debug);
                  }
                throwHandshakeFailure();
              }
            ckex = new ClientKeyExchange(A);
          }
        msg = new Handshake(Handshake.Type.CLIENT_KEY_EXCHANGE, ckex);
        if (DEBUG_HANDSHAKE_LAYER) debug.println(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)
                  {
                    kae.printStackTrace(debug);
                  }
                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)
                  {
                    x.printStackTrace(debug);
                  }
                throwHandshakeFailure();
              }
            finally
              {
                clientKA = null;
              }
          }
        if (DEBUG_KEY_EXCHANGE)
          {
            debug.println("preMasterSecret= " +
                          Util.toHexString(preMasterSecret, ':'));
            debug.println("client.random  = " +
                          Util.toHexString(clientRandom.getEncoded(), ':'));
            debug.println("server.random  = " +
                          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)
          {
            debug.println("masterSecret=" + 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(),
                                            CSPRNG.SYSTEM_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) debug.println(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) debug.println(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) debug.println(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) debug.println(msg);
        msg.write(dout, version);
        dout.flush();
      }

    handshakeCompleted();
  }

  /**
   * Perform the server handshake.
   */
02139   private void doServerHandshake() throws IOException
  {
    if (DEBUG_HANDSHAKE_LAYER)
      {
        debug.println("doing server handshake in "+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) debug.println(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)
      {
        debug.println("saved sessions = " + 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;
          }
        else
          {
            if (DEBUG_HANDSHAKE_LAYER)
              {
                debug.println("rejected session");
                debug.println("hosts equal? " + session.getPeerHost().equals(remoteHost));
                debug.println("same suites? " + old.enabledProtocols.contains(session.protocol));
              }
            session = old;
            session.peerHost = remoteHost;
            newSession = true;
          }
      }
    else if (DEBUG_HANDSHAKE_LAYER)
      {
        debug.println("rejected session");
        debug.println("have session id? " + sessionContext.containsSessionID(new Session.ID(clientHello.getSessionId())));
        debug.println("saved sessions = " + 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) debug.println(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) debug.println(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)
                  kae.printStackTrace(debug);
                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,
                     CSPRNG.SYSTEM_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)
                  {
                    kae.printStackTrace(debug);
                  }
                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)
                  {
                    x.printStackTrace(debug);
                  }
                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) debug.println(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) debug.println(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) debug.println(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) debug.println(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)
                  {
                    x.printStackTrace(debug);
                  }
                // Generate a fake pre-master secret if the RSA decryption
                // fails.
                preMasterSecret = Util.concat(version.getEncoded(), new byte[46]);
                try
                  {
                    CSPRNG.SYSTEM_RANDOM.nextBytes (preMasterSecret,
                      preMasterSecret.length - 46, 46);
                  }
                catch (LimitReachedException lre)
                  {
                  }
              }
          }
        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)
                  {
                    kae.printStackTrace(debug);
                  }
                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)
              {
                debug.println("client A=" + 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)
                  {
                    x.printStackTrace(debug);
                  }
                throwHandshakeFailure();
              }
            finally
              {
                serverKA = null;
              }
          }

        if (DEBUG_KEY_EXCHANGE)
          {
            debug.println("preMasterSecret= " +
                          Util.toHexString(preMasterSecret, ':'));
            debug.println("client.random  = " +
                          Util.toHexString(clientRandom.getEncoded(), ':'));
            debug.println("server.random  = " +
                          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)
          {
            debug.println("masterSecret=" + 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[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 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) debug.println(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) debug.println(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) debug.println(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.
   */
03045   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)
      {
        debug.println("Generated keys:");
        for (int i = 0; i < keyMaterial.length; i++)
          {
            debug.println(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.
   */
03167   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.
   */
03255   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.
   */
03272   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.
   */
03289   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.
   */
03329   private CipherSuite selectSuite(List suites, ProtocolVersion version)
    throws IOException
  {
    if (DEBUG_HANDSHAKE_LAYER)
      debug.println("selectSuite req=" + suites + " avail=" + 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)
              debug.println(mySuite + " == " + herSuite);
            if (mySuite.getSignature() != "anon" && session.keyManager != null &&
                session.keyManager.chooseServerAlias(mySuite.getAuthType(), null, null) == null)
              {
                if (DEBUG_HANDSHAKE_LAYER)
                  debug.println(mySuite + ": no certificate/private key");
                continue;
              }
            if (mySuite.getKeyExchange() == "SRP")
              {
                if (session.getValue("srp-username") == null)
                  {
                    if (DEBUG_HANDSHAKE_LAYER)
                      debug.println("no SRP username");
                    srpSuiteNoUser = true;
                    continue;
                  }
                if (session.srpTrustManager == null)
                  {
                    if (DEBUG_HANDSHAKE_LAYER)
                      debug.println("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.
   */
03395   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.
   */
03421   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.
   */
03446   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.
   */
03482   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.
   */
03493   private void fatal() throws IOException
  {
    if (session != null)
      {
        session.invalidate();
      }
//     recordInput.setRunning(false);
//     recordOutput.setRunning(false);
    if (underlyingSocket != null)
      {
        underlyingSocket.close();
      }
    else
      {
        super.close();
      }
  }
}

Generated by  Doxygen 1.6.0   Back to index