mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			1378 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			1378 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			Java
		
	
	
	
/* ServerHandshake.java -- the server-side handshake.
 | 
						|
   Copyright (C) 2006  Free Software Foundation, Inc.
 | 
						|
 | 
						|
This file is a part of GNU Classpath.
 | 
						|
 | 
						|
GNU Classpath is free software; you can redistribute it and/or modify
 | 
						|
it under the terms of the GNU General Public License as published by
 | 
						|
the Free Software Foundation; either version 2 of the License, or (at
 | 
						|
your option) any later version.
 | 
						|
 | 
						|
GNU Classpath is distributed in the hope that it will be useful, but
 | 
						|
WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
General Public License for more details.
 | 
						|
 | 
						|
You should have received a copy of the GNU General Public License
 | 
						|
along with GNU Classpath; if not, write to the Free Software
 | 
						|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
 | 
						|
USA
 | 
						|
 | 
						|
Linking this library statically or dynamically with other modules is
 | 
						|
making a combined work based on this library.  Thus, the terms and
 | 
						|
conditions of the GNU General Public License cover the whole
 | 
						|
combination.
 | 
						|
 | 
						|
As a special exception, the copyright holders of this library give you
 | 
						|
permission to link this library with independent modules to produce an
 | 
						|
executable, regardless of the license terms of these independent
 | 
						|
modules, and to copy and distribute the resulting executable under
 | 
						|
terms of your choice, provided that you also meet, for each linked
 | 
						|
independent module, the terms and conditions of the license of that
 | 
						|
module.  An independent module is a module which is not derived from
 | 
						|
or based on this library.  If you modify this library, you may extend
 | 
						|
this exception to your version of the library, but you are not
 | 
						|
obligated to do so.  If you do not wish to do so, delete this
 | 
						|
exception statement from your version.  */
 | 
						|
 | 
						|
 | 
						|
package gnu.javax.net.ssl.provider;
 | 
						|
 | 
						|
import static gnu.javax.net.ssl.provider.Handshake.Type.*;
 | 
						|
import static gnu.javax.net.ssl.provider.KeyExchangeAlgorithm.*;
 | 
						|
import static gnu.javax.net.ssl.provider.ServerHandshake.State.*;
 | 
						|
 | 
						|
import gnu.classpath.debug.Component;
 | 
						|
import gnu.java.security.action.GetSecurityPropertyAction;
 | 
						|
import gnu.javax.crypto.key.dh.GnuDHPublicKey;
 | 
						|
import gnu.javax.net.ssl.AbstractSessionContext;
 | 
						|
import gnu.javax.net.ssl.Session;
 | 
						|
import gnu.javax.net.ssl.provider.Alert.Description;
 | 
						|
import gnu.javax.net.ssl.provider.CertificateRequest.ClientCertificateType;
 | 
						|
 | 
						|
import java.nio.ByteBuffer;
 | 
						|
 | 
						|
import java.security.AccessController;
 | 
						|
import java.security.InvalidAlgorithmParameterException;
 | 
						|
import java.security.InvalidKeyException;
 | 
						|
import java.security.KeyManagementException;
 | 
						|
import java.security.KeyPair;
 | 
						|
import java.security.KeyPairGenerator;
 | 
						|
import java.security.MessageDigest;
 | 
						|
import java.security.NoSuchAlgorithmException;
 | 
						|
import java.security.Principal;
 | 
						|
import java.security.PrivateKey;
 | 
						|
import java.security.SignatureException;
 | 
						|
import java.security.cert.CertificateException;
 | 
						|
import java.security.cert.X509Certificate;
 | 
						|
import java.util.ArrayList;
 | 
						|
import java.util.Arrays;
 | 
						|
import java.util.HashSet;
 | 
						|
import java.util.List;
 | 
						|
import java.util.logging.Level;
 | 
						|
import java.util.zip.Deflater;
 | 
						|
import java.util.zip.Inflater;
 | 
						|
 | 
						|
import javax.crypto.BadPaddingException;
 | 
						|
import javax.crypto.Cipher;
 | 
						|
import javax.crypto.IllegalBlockSizeException;
 | 
						|
import javax.crypto.NoSuchPaddingException;
 | 
						|
import javax.crypto.SecretKey;
 | 
						|
import javax.crypto.interfaces.DHPrivateKey;
 | 
						|
import javax.crypto.interfaces.DHPublicKey;
 | 
						|
import javax.crypto.spec.DHParameterSpec;
 | 
						|
import javax.crypto.spec.SecretKeySpec;
 | 
						|
import javax.net.ssl.SSLException;
 | 
						|
import javax.net.ssl.SSLPeerUnverifiedException;
 | 
						|
import javax.net.ssl.SSLSession;
 | 
						|
import javax.net.ssl.X509ExtendedKeyManager;
 | 
						|
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
 | 
						|
import javax.security.auth.x500.X500Principal;
 | 
						|
 | 
						|
class ServerHandshake extends AbstractHandshake
 | 
						|
{
 | 
						|
  /**
 | 
						|
   * Handshake state enumeration.
 | 
						|
   */
 | 
						|
  static enum State
 | 
						|
  {
 | 
						|
    WRITE_HELLO_REQUEST (true, false),
 | 
						|
    WRITE_SERVER_HELLO (true, false),
 | 
						|
    WRITE_CERTIFICATE (true, false),
 | 
						|
    WRITE_SERVER_KEY_EXCHANGE (true, false),
 | 
						|
    WRITE_CERTIFICATE_REQUEST (true, false),
 | 
						|
    WRITE_SERVER_HELLO_DONE (true, false),
 | 
						|
    WRITE_FINISHED (true, false),
 | 
						|
    READ_CLIENT_HELLO (false, true),
 | 
						|
    READ_CERTIFICATE (false, true),
 | 
						|
    READ_CLIENT_KEY_EXCHANGE (false, true),
 | 
						|
    READ_CERTIFICATE_VERIFY (false, true),
 | 
						|
    READ_FINISHED (false, true),
 | 
						|
    DONE (false, false);
 | 
						|
 | 
						|
    private final boolean isWriteState;
 | 
						|
    private final boolean isReadState;
 | 
						|
 | 
						|
    private State(final boolean isWriteState, final boolean isReadState)
 | 
						|
    {
 | 
						|
      this.isWriteState = isWriteState;
 | 
						|
      this.isReadState = isReadState;
 | 
						|
    }
 | 
						|
 | 
						|
    boolean isReadState()
 | 
						|
    {
 | 
						|
      return isReadState;
 | 
						|
    }
 | 
						|
 | 
						|
    boolean isWriteState()
 | 
						|
    {
 | 
						|
      return isWriteState;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private State state;
 | 
						|
 | 
						|
  /* Handshake result fields. */
 | 
						|
  private ByteBuffer outBuffer;
 | 
						|
  private boolean clientHadExtensions = false;
 | 
						|
  private boolean continuedSession = false;
 | 
						|
  private ServerNameList requestedNames = null;
 | 
						|
  private String keyAlias = null;
 | 
						|
  private X509Certificate clientCert = null;
 | 
						|
  private X509Certificate localCert = null;
 | 
						|
  private boolean helloV2 = false;
 | 
						|
  private KeyPair dhPair;
 | 
						|
  private PrivateKey serverKey;
 | 
						|
 | 
						|
  // Delegated tasks we use.
 | 
						|
  private GenDH genDH;
 | 
						|
  private CertVerifier certVerifier;
 | 
						|
  private CertLoader certLoader;
 | 
						|
  private DelegatedTask keyExchangeTask;
 | 
						|
 | 
						|
  ServerHandshake (boolean writeHelloRequest, final SSLEngineImpl engine)
 | 
						|
    throws NoSuchAlgorithmException
 | 
						|
  {
 | 
						|
    super(engine);
 | 
						|
    if (writeHelloRequest)
 | 
						|
      state = WRITE_HELLO_REQUEST;
 | 
						|
    else
 | 
						|
      state = READ_CLIENT_HELLO;
 | 
						|
    handshakeOffset = 0;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Choose the protocol version. Here we choose the largest protocol
 | 
						|
   * version we support that is not greater than the client's
 | 
						|
   * requested version.
 | 
						|
   */
 | 
						|
  private static ProtocolVersion chooseProtocol (final ProtocolVersion clientVersion,
 | 
						|
                                                 final String[] enabledVersions)
 | 
						|
    throws SSLException
 | 
						|
  {
 | 
						|
    ProtocolVersion version = null;
 | 
						|
    for (int i = 0; i < enabledVersions.length; i++)
 | 
						|
      {
 | 
						|
        ProtocolVersion v = ProtocolVersion.forName (enabledVersions[i]);
 | 
						|
        if (v.compareTo (clientVersion) <= 0)
 | 
						|
          {
 | 
						|
            if (version == null
 | 
						|
                || v.compareTo (version) > 0)
 | 
						|
              version = v;
 | 
						|
          }
 | 
						|
      }
 | 
						|
 | 
						|
    // The client requested a protocol version too old, or no protocol
 | 
						|
    // versions are enabled.
 | 
						|
    if (version == null)
 | 
						|
      throw new SSLException ("no acceptable protocol version available");
 | 
						|
    return version;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Choose the first cipher suite in the client's requested list that
 | 
						|
   * we have enabled.
 | 
						|
   */
 | 
						|
  private CipherSuite chooseSuite (final CipherSuiteList clientSuites,
 | 
						|
                                   final String[] enabledSuites,
 | 
						|
                                   final ProtocolVersion version)
 | 
						|
    throws SSLException
 | 
						|
  {
 | 
						|
    // Figure out which SignatureAlgorithms we can support.
 | 
						|
    HashSet<KeyExchangeAlgorithm> kexes = new HashSet<KeyExchangeAlgorithm>(8);
 | 
						|
 | 
						|
    kexes.add(NONE);
 | 
						|
    X509ExtendedKeyManager km = engine.contextImpl.keyManager;
 | 
						|
    if (km != null)
 | 
						|
      {
 | 
						|
        if (km.getServerAliases(DH_DSS.name(), null).length > 0)
 | 
						|
          kexes.add(DH_DSS);
 | 
						|
        if (km.getServerAliases(DH_RSA.name(), null).length > 0)
 | 
						|
          kexes.add(DH_RSA);
 | 
						|
        if (km.getServerAliases(DHE_DSS.name(), null).length > 0)
 | 
						|
          kexes.add(DHE_DSS);
 | 
						|
        if (km.getServerAliases(DHE_RSA.name(), null).length > 0)
 | 
						|
          kexes.add(DHE_RSA);
 | 
						|
        if (km.getServerAliases(RSA.name(), null).length > 0)
 | 
						|
          kexes.add(RSA);
 | 
						|
        if (km.getServerAliases(RSA_PSK.name(), null).length > 0
 | 
						|
            && engine.contextImpl.pskManager != null)
 | 
						|
          kexes.add(RSA_PSK);
 | 
						|
      }
 | 
						|
    if (engine.contextImpl.pskManager != null)
 | 
						|
      {
 | 
						|
        kexes.add(DHE_PSK);
 | 
						|
        kexes.add(PSK);
 | 
						|
      }
 | 
						|
 | 
						|
    if (Debug.DEBUG)
 | 
						|
      logger.logv(Component.SSL_HANDSHAKE,
 | 
						|
                  "we have certs for key exchange algorithms {0}", kexes);
 | 
						|
 | 
						|
    HashSet<CipherSuite> suites = new HashSet<CipherSuite>();
 | 
						|
    for (String s : enabledSuites)
 | 
						|
      {
 | 
						|
        CipherSuite suite = CipherSuite.forName(s);
 | 
						|
        if (suite == null)
 | 
						|
          continue;
 | 
						|
        if (!kexes.contains(suite.keyExchangeAlgorithm()))
 | 
						|
          continue;
 | 
						|
        suites.add(suite);
 | 
						|
      }
 | 
						|
    for (CipherSuite suite : clientSuites)
 | 
						|
      {
 | 
						|
        CipherSuite resolved = suite.resolve();
 | 
						|
        if (!resolved.isResolved())
 | 
						|
          continue;
 | 
						|
        if (suites.contains(resolved))
 | 
						|
          return resolved;
 | 
						|
      }
 | 
						|
 | 
						|
    // We didn't find a match?
 | 
						|
    throw new AlertException(new Alert(Alert.Level.FATAL,
 | 
						|
                                       Alert.Description.INSUFFICIENT_SECURITY));
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Choose a compression method that we support, among the client's
 | 
						|
   * requested compression methods. We prefer ZLIB over NONE in this
 | 
						|
   * implementation.
 | 
						|
   *
 | 
						|
   * XXX Maybe consider implementing lzo (GNUTLS supports that).
 | 
						|
   */
 | 
						|
  private static CompressionMethod chooseCompression (final CompressionMethodList comps)
 | 
						|
    throws SSLException
 | 
						|
  {
 | 
						|
    GetSecurityPropertyAction gspa
 | 
						|
      = new GetSecurityPropertyAction("jessie.enable.compression");
 | 
						|
    String enable = AccessController.doPrivileged(gspa);
 | 
						|
    // Scan for ZLIB first.
 | 
						|
    if (Boolean.valueOf(enable))
 | 
						|
      {
 | 
						|
        for (CompressionMethod cm : comps)
 | 
						|
          {
 | 
						|
            if (cm.equals (CompressionMethod.ZLIB))
 | 
						|
              return CompressionMethod.ZLIB;
 | 
						|
          }
 | 
						|
      }
 | 
						|
    for (CompressionMethod cm : comps)
 | 
						|
      {
 | 
						|
        if (cm.equals (CompressionMethod.NULL))
 | 
						|
          return CompressionMethod.NULL;
 | 
						|
      }
 | 
						|
 | 
						|
    throw new SSLException ("no supported compression method");
 | 
						|
  }
 | 
						|
 | 
						|
  protected @Override boolean doHash()
 | 
						|
  {
 | 
						|
    boolean b = helloV2;
 | 
						|
    helloV2 = false;
 | 
						|
    return (state != WRITE_HELLO_REQUEST) && !b;
 | 
						|
  }
 | 
						|
 | 
						|
  public @Override HandshakeStatus implHandleInput()
 | 
						|
    throws SSLException
 | 
						|
  {
 | 
						|
    if (state == DONE)
 | 
						|
      return HandshakeStatus.FINISHED;
 | 
						|
 | 
						|
    if (state.isWriteState()
 | 
						|
        || (outBuffer != null && outBuffer.hasRemaining()))
 | 
						|
      return HandshakeStatus.NEED_WRAP;
 | 
						|
 | 
						|
    // Copy the current buffer, and prepare it for reading.
 | 
						|
    ByteBuffer buffer = handshakeBuffer.duplicate ();
 | 
						|
    buffer.flip();
 | 
						|
    buffer.position(handshakeOffset);
 | 
						|
    Handshake handshake = new Handshake(buffer.slice(),
 | 
						|
                                        engine.session().suite,
 | 
						|
                                        engine.session().version);
 | 
						|
 | 
						|
    if (Debug.DEBUG)
 | 
						|
      logger.logv(Component.SSL_HANDSHAKE, "processing in state {0}:\n{1}",
 | 
						|
                  state, handshake);
 | 
						|
 | 
						|
    switch (state)
 | 
						|
      {
 | 
						|
        // Client Hello.
 | 
						|
        //
 | 
						|
        // This message is sent by the client to initiate a new handshake.
 | 
						|
        // On a new connection, it is the first handshake message sent.
 | 
						|
        //
 | 
						|
        // The state of the handshake, after this message is processed,
 | 
						|
        // will have a protocol version, cipher suite, compression method,
 | 
						|
        // session ID, and various extensions (that the server also
 | 
						|
        // supports).
 | 
						|
        case READ_CLIENT_HELLO:
 | 
						|
          if (handshake.type () != CLIENT_HELLO)
 | 
						|
            throw new AlertException(new Alert(Alert.Level.FATAL,
 | 
						|
                                               Alert.Description.UNEXPECTED_MESSAGE));
 | 
						|
 | 
						|
          {
 | 
						|
            ClientHello hello = (ClientHello) handshake.body ();
 | 
						|
            engine.session().version
 | 
						|
              = chooseProtocol (hello.version (),
 | 
						|
                                engine.getEnabledProtocols ());
 | 
						|
            engine.session().suite =
 | 
						|
              chooseSuite (hello.cipherSuites (),
 | 
						|
                           engine.getEnabledCipherSuites (),
 | 
						|
                           engine.session().version);
 | 
						|
            compression = chooseCompression (hello.compressionMethods ());
 | 
						|
            if (Debug.DEBUG)
 | 
						|
              logger.logv(Component.SSL_HANDSHAKE,
 | 
						|
                          "chose version:{0} suite:{1} compression:{2}",
 | 
						|
                          engine.session().version, engine.session().suite,
 | 
						|
                          compression);
 | 
						|
            clientRandom = hello.random().copy();
 | 
						|
            byte[] sessionId = hello.sessionId();
 | 
						|
            if (hello.hasExtensions())
 | 
						|
              {
 | 
						|
                ExtensionList exts = hello.extensions();
 | 
						|
                clientHadExtensions = exts.size() > 0;
 | 
						|
                for (Extension e : hello.extensions())
 | 
						|
                  {
 | 
						|
                    Extension.Type type = e.type();
 | 
						|
                    if (type == null)
 | 
						|
                      continue;
 | 
						|
                    switch (type)
 | 
						|
                    {
 | 
						|
                    case TRUNCATED_HMAC:
 | 
						|
                      engine.session().setTruncatedMac(true);
 | 
						|
                      break;
 | 
						|
 | 
						|
                    case MAX_FRAGMENT_LENGTH:
 | 
						|
                      MaxFragmentLength len = (MaxFragmentLength) e.value();
 | 
						|
                      engine.session().maxLength = len;
 | 
						|
                      engine.session().setApplicationBufferSize(len.maxLength());
 | 
						|
                      break;
 | 
						|
 | 
						|
                    case SERVER_NAME:
 | 
						|
                      requestedNames = (ServerNameList) e.value();
 | 
						|
                      List<String> names
 | 
						|
                        = new ArrayList<String>(requestedNames.size());
 | 
						|
                      for (ServerNameList.ServerName name : requestedNames)
 | 
						|
                        names.add(name.name());
 | 
						|
                      engine.session().putValue("gnu.javax.net.ssl.RequestedServerNames", names);
 | 
						|
                      break;
 | 
						|
 | 
						|
                    default:
 | 
						|
                      logger.log(Level.INFO, "skipping unsupported extension {0}", e);
 | 
						|
                    }
 | 
						|
                  }
 | 
						|
              }
 | 
						|
            AbstractSessionContext sessions = (AbstractSessionContext)
 | 
						|
              engine.contextImpl.engineGetServerSessionContext();
 | 
						|
            SSLSession s = sessions.getSession(sessionId);
 | 
						|
            if (Debug.DEBUG)
 | 
						|
              logger.logv(Component.SSL_HANDSHAKE, "looked up saved session {0}", s);
 | 
						|
            if (s != null && s.isValid() && (s instanceof SessionImpl))
 | 
						|
              {
 | 
						|
                engine.setSession((SessionImpl) s);
 | 
						|
                continuedSession = true;
 | 
						|
              }
 | 
						|
            else
 | 
						|
              {
 | 
						|
                // We *may* wind up with a badly seeded PRNG, and emit the
 | 
						|
                // same session ID over and over (this did happen to me,
 | 
						|
                // so we add this sanity check just in case).
 | 
						|
                if (engine.session().id().equals(new Session.ID(sessionId)))
 | 
						|
                  {
 | 
						|
                    byte[] newId = new byte[32];
 | 
						|
                    engine.session().random().nextBytes(newId);
 | 
						|
                    engine.session().setId(new Session.ID(newId));
 | 
						|
                  }
 | 
						|
                sessions.put(engine.session());
 | 
						|
              }
 | 
						|
            state = WRITE_SERVER_HELLO;
 | 
						|
          }
 | 
						|
          break;
 | 
						|
 | 
						|
        // Certificate.
 | 
						|
        //
 | 
						|
        // This message is sent by the client if the server had previously
 | 
						|
        // requested that the client authenticate itself with a certificate,
 | 
						|
        // and if the client has an appropriate certificate available.
 | 
						|
        //
 | 
						|
        // Processing this message will save the client's certificate,
 | 
						|
        // rejecting it if the certificate is not trusted, in preparation
 | 
						|
        // for the certificate verify message that will follow.
 | 
						|
        case READ_CERTIFICATE:
 | 
						|
          {
 | 
						|
            if (handshake.type() != CERTIFICATE)
 | 
						|
              {
 | 
						|
                if (engine.getNeedClientAuth()) // XXX throw better exception.
 | 
						|
                  throw new SSLException("client auth required");
 | 
						|
                state = READ_CLIENT_KEY_EXCHANGE;
 | 
						|
                return HandshakeStatus.NEED_UNWRAP;
 | 
						|
              }
 | 
						|
 | 
						|
            Certificate cert = (Certificate) handshake.body();
 | 
						|
            try
 | 
						|
              {
 | 
						|
                engine.session().setPeerVerified(false);
 | 
						|
                X509Certificate[] chain
 | 
						|
                  = cert.certificates().toArray(new X509Certificate[0]);
 | 
						|
                if (chain.length == 0)
 | 
						|
                  throw new CertificateException("no certificates in chain");
 | 
						|
                certVerifier = new CertVerifier(false, chain);
 | 
						|
                tasks.add(certVerifier);
 | 
						|
                engine.session().setPeerCertificates(chain);
 | 
						|
                clientCert = chain[0];
 | 
						|
                // Delay setting 'peerVerified' until CertificateVerify.
 | 
						|
              }
 | 
						|
            catch (CertificateException ce)
 | 
						|
              {
 | 
						|
                if (engine.getNeedClientAuth())
 | 
						|
                  {
 | 
						|
                    SSLPeerUnverifiedException x
 | 
						|
                      = new SSLPeerUnverifiedException("client certificates could not be verified");
 | 
						|
                    x.initCause(ce);
 | 
						|
                    throw x;
 | 
						|
                  }
 | 
						|
              }
 | 
						|
            catch (NoSuchAlgorithmException nsae)
 | 
						|
              {
 | 
						|
                throw new SSLException(nsae);
 | 
						|
              }
 | 
						|
            state = READ_CLIENT_KEY_EXCHANGE;
 | 
						|
          }
 | 
						|
          break;
 | 
						|
 | 
						|
        // Client Key Exchange.
 | 
						|
        //
 | 
						|
        // The client's key exchange. This message is sent either following
 | 
						|
        // the certificate message, or if no certificate is available or
 | 
						|
        // requested, following the server's hello done message.
 | 
						|
        //
 | 
						|
        // After receipt of this message, the session keys for this
 | 
						|
        // session will have been created.
 | 
						|
        case READ_CLIENT_KEY_EXCHANGE:
 | 
						|
          {
 | 
						|
            if (handshake.type() != CLIENT_KEY_EXCHANGE)
 | 
						|
              throw new SSLException("expecting client key exchange");
 | 
						|
            ClientKeyExchange kex = (ClientKeyExchange) handshake.body();
 | 
						|
 | 
						|
            KeyExchangeAlgorithm alg = engine.session().suite.keyExchangeAlgorithm();
 | 
						|
            switch (alg)
 | 
						|
              {
 | 
						|
                case DHE_DSS:
 | 
						|
                case DHE_RSA:
 | 
						|
                case DH_anon:
 | 
						|
                  {
 | 
						|
                    ClientDiffieHellmanPublic pub = (ClientDiffieHellmanPublic)
 | 
						|
                      kex.exchangeKeys();
 | 
						|
                    DHPublicKey myKey = (DHPublicKey) dhPair.getPublic();
 | 
						|
                    DHPublicKey clientKey =
 | 
						|
                      new GnuDHPublicKey(null, myKey.getParams().getP(),
 | 
						|
                                         myKey.getParams().getG(),
 | 
						|
                                         pub.publicValue());
 | 
						|
                    keyExchangeTask = new DHPhase(clientKey);
 | 
						|
                    tasks.add(keyExchangeTask);
 | 
						|
                  }
 | 
						|
                  break;
 | 
						|
 | 
						|
                case RSA:
 | 
						|
                  {
 | 
						|
                    EncryptedPreMasterSecret secret = (EncryptedPreMasterSecret)
 | 
						|
                      kex.exchangeKeys();
 | 
						|
                    keyExchangeTask = new RSAKeyExchange(secret.encryptedSecret());
 | 
						|
                    tasks.add(keyExchangeTask);
 | 
						|
                  }
 | 
						|
                  break;
 | 
						|
 | 
						|
                case PSK:
 | 
						|
                  {
 | 
						|
                    ClientPSKParameters params = (ClientPSKParameters)
 | 
						|
                      kex.exchangeKeys();
 | 
						|
                    generatePSKSecret(params.identity(), null, false);
 | 
						|
                  }
 | 
						|
                  break;
 | 
						|
 | 
						|
                case DHE_PSK:
 | 
						|
                  {
 | 
						|
                    ClientDHE_PSKParameters params = (ClientDHE_PSKParameters)
 | 
						|
                      kex.exchangeKeys();
 | 
						|
                    DHPublicKey serverKey = (DHPublicKey) dhPair.getPublic();
 | 
						|
                    DHPublicKey clientKey =
 | 
						|
                      new GnuDHPublicKey(null, serverKey.getParams().getP(),
 | 
						|
                                         serverKey.getParams().getG(),
 | 
						|
                                         params.params().publicValue());
 | 
						|
                    SecretKey psk = null;
 | 
						|
                    try
 | 
						|
                      {
 | 
						|
                        psk = engine.contextImpl.pskManager.getKey(params.identity());
 | 
						|
                      }
 | 
						|
                    catch (KeyManagementException kme)
 | 
						|
                      {
 | 
						|
                      }
 | 
						|
                    keyExchangeTask = new DHE_PSKGen(clientKey, psk, false);
 | 
						|
                    tasks.add(keyExchangeTask);
 | 
						|
                  }
 | 
						|
                  break;
 | 
						|
 | 
						|
                case RSA_PSK:
 | 
						|
                  {
 | 
						|
                    ClientRSA_PSKParameters params = (ClientRSA_PSKParameters)
 | 
						|
                      kex.exchangeKeys();
 | 
						|
                    SecretKey psk = null;
 | 
						|
                    try
 | 
						|
                      {
 | 
						|
                        psk = engine.contextImpl.pskManager.getKey(params.identity());
 | 
						|
                      }
 | 
						|
                    catch (KeyManagementException kme)
 | 
						|
                      {
 | 
						|
                      }
 | 
						|
                    if (psk == null)
 | 
						|
                      {
 | 
						|
                        byte[] fakeKey = new byte[16];
 | 
						|
                        engine.session().random().nextBytes(fakeKey);
 | 
						|
                        psk = new SecretKeySpec(fakeKey, "DHE_PSK");
 | 
						|
                      }
 | 
						|
                    keyExchangeTask =
 | 
						|
                      new RSA_PSKExchange(params.secret().encryptedSecret(), psk);
 | 
						|
                    tasks.add(keyExchangeTask);
 | 
						|
                  }
 | 
						|
                  break;
 | 
						|
 | 
						|
                case NONE:
 | 
						|
                  {
 | 
						|
                    Inflater inflater = null;
 | 
						|
                    Deflater deflater = null;
 | 
						|
                    if (compression == CompressionMethod.ZLIB)
 | 
						|
                      {
 | 
						|
                        inflater = new Inflater();
 | 
						|
                        deflater = new Deflater();
 | 
						|
                      }
 | 
						|
                    inParams = new InputSecurityParameters(null, null, inflater,
 | 
						|
                                                           engine.session(),
 | 
						|
                                                           engine.session().suite);
 | 
						|
                    outParams = new OutputSecurityParameters(null, null, deflater,
 | 
						|
                                                             engine.session(),
 | 
						|
                                                             engine.session().suite);
 | 
						|
                    engine.session().privateData.masterSecret = new byte[0];
 | 
						|
                  }
 | 
						|
                  break;
 | 
						|
              }
 | 
						|
            // XXX SRP
 | 
						|
 | 
						|
            if (clientCert != null)
 | 
						|
              state = READ_CERTIFICATE_VERIFY;
 | 
						|
            else
 | 
						|
              state = READ_FINISHED;
 | 
						|
          }
 | 
						|
          break;
 | 
						|
 | 
						|
        // Certificate Verify.
 | 
						|
        //
 | 
						|
        // This message is sent following the client key exchange message,
 | 
						|
        // but only when the client included its certificate in a previous
 | 
						|
        // message.
 | 
						|
        //
 | 
						|
        // After receipt of this message, the client's certificate (and,
 | 
						|
        // to a degree, the client's identity) will have been verified.
 | 
						|
        case READ_CERTIFICATE_VERIFY:
 | 
						|
          {
 | 
						|
            if (handshake.type() != CERTIFICATE_VERIFY)
 | 
						|
              throw new SSLException("expecting certificate verify message");
 | 
						|
 | 
						|
            CertificateVerify verify = (CertificateVerify) handshake.body();
 | 
						|
            try
 | 
						|
              {
 | 
						|
                verifyClient(verify.signature());
 | 
						|
                if (certVerifier != null && certVerifier.verified())
 | 
						|
                  engine.session().setPeerVerified(true);
 | 
						|
              }
 | 
						|
            catch (SignatureException se)
 | 
						|
              {
 | 
						|
                if (engine.getNeedClientAuth())
 | 
						|
                  throw new SSLException("client auth failed", se);
 | 
						|
              }
 | 
						|
            if (continuedSession)
 | 
						|
              {
 | 
						|
                engine.changeCipherSpec();
 | 
						|
                state = WRITE_FINISHED;
 | 
						|
              }
 | 
						|
            else
 | 
						|
              state = READ_FINISHED;
 | 
						|
          }
 | 
						|
          break;
 | 
						|
 | 
						|
        // Finished.
 | 
						|
        //
 | 
						|
        // This message is sent immediately following the change cipher
 | 
						|
        // spec message (which is sent outside of the handshake layer).
 | 
						|
        // After receipt of this message, the session keys for the client
 | 
						|
        // side will have been verified (this is the first message the
 | 
						|
        // client sends encrypted and authenticated with the newly
 | 
						|
        // negotiated keys).
 | 
						|
        //
 | 
						|
        // In the case of a continued session, the client sends its
 | 
						|
        // finished message first. Otherwise, the server will send its
 | 
						|
        // finished message first.
 | 
						|
        case READ_FINISHED:
 | 
						|
          {
 | 
						|
            if (handshake.type() != FINISHED)
 | 
						|
              throw new AlertException(new Alert(Alert.Level.FATAL,
 | 
						|
                                                 Description.UNEXPECTED_MESSAGE));
 | 
						|
 | 
						|
            Finished clientFinished = (Finished) handshake.body();
 | 
						|
 | 
						|
            MessageDigest md5copy = null;
 | 
						|
            MessageDigest shacopy = null;
 | 
						|
            try
 | 
						|
              {
 | 
						|
                md5copy = (MessageDigest) md5.clone();
 | 
						|
                shacopy = (MessageDigest) sha.clone();
 | 
						|
              }
 | 
						|
            catch (CloneNotSupportedException cnse)
 | 
						|
              {
 | 
						|
                // We're improperly configured to use a non-cloneable
 | 
						|
                // md5/sha-1, OR there's a runtime bug.
 | 
						|
                throw new SSLException(cnse);
 | 
						|
              }
 | 
						|
            Finished serverFinished =
 | 
						|
              new Finished(generateFinished(md5copy, shacopy,
 | 
						|
                                            true, engine.session()),
 | 
						|
                                            engine.session().version);
 | 
						|
 | 
						|
            if (Debug.DEBUG)
 | 
						|
              logger.log(Component.SSL_HANDSHAKE, "server finished: {0}",
 | 
						|
                         serverFinished);
 | 
						|
 | 
						|
            if (engine.session().version == ProtocolVersion.SSL_3)
 | 
						|
              {
 | 
						|
                if (!Arrays.equals(clientFinished.md5Hash(),
 | 
						|
                                   serverFinished.md5Hash())
 | 
						|
                    || !Arrays.equals(clientFinished.shaHash(),
 | 
						|
                                      serverFinished.shaHash()))
 | 
						|
                  {
 | 
						|
                    engine.session().invalidate();
 | 
						|
                    throw new SSLException("session verify failed");
 | 
						|
                  }
 | 
						|
              }
 | 
						|
            else
 | 
						|
              {
 | 
						|
                if (!Arrays.equals(clientFinished.verifyData(),
 | 
						|
                                   serverFinished.verifyData()))
 | 
						|
                  {
 | 
						|
                    engine.session().invalidate();
 | 
						|
                    throw new SSLException("session verify failed");
 | 
						|
                  }
 | 
						|
              }
 | 
						|
 | 
						|
            if (continuedSession)
 | 
						|
              state = DONE;
 | 
						|
            else
 | 
						|
              {
 | 
						|
                engine.changeCipherSpec();
 | 
						|
                state = WRITE_FINISHED;
 | 
						|
              }
 | 
						|
          }
 | 
						|
          break;
 | 
						|
      }
 | 
						|
 | 
						|
    handshakeOffset += handshake.length() + 4;
 | 
						|
 | 
						|
    if (!tasks.isEmpty())
 | 
						|
      return HandshakeStatus.NEED_TASK;
 | 
						|
    if (state.isReadState())
 | 
						|
      return HandshakeStatus.NEED_UNWRAP;
 | 
						|
    if (state.isWriteState())
 | 
						|
      return HandshakeStatus.NEED_WRAP;
 | 
						|
 | 
						|
    return HandshakeStatus.FINISHED;
 | 
						|
  }
 | 
						|
 | 
						|
  public @Override HandshakeStatus implHandleOutput (ByteBuffer fragment)
 | 
						|
    throws SSLException
 | 
						|
  {
 | 
						|
    if (Debug.DEBUG)
 | 
						|
      logger.logv(Component.SSL_HANDSHAKE,
 | 
						|
                  "handle output state: {0}; output fragment: {1}",
 | 
						|
                  state, fragment);
 | 
						|
 | 
						|
    // Drain the output buffer, if it needs it.
 | 
						|
    if (outBuffer != null && outBuffer.hasRemaining())
 | 
						|
      {
 | 
						|
        int l = Math.min(fragment.remaining(), outBuffer.remaining());
 | 
						|
        fragment.put((ByteBuffer) outBuffer.duplicate().limit(outBuffer.position() + l));
 | 
						|
        outBuffer.position(outBuffer.position() + l);
 | 
						|
      }
 | 
						|
 | 
						|
    if (!fragment.hasRemaining())
 | 
						|
      {
 | 
						|
        if (state.isWriteState() || outBuffer.hasRemaining())
 | 
						|
          return HandshakeStatus.NEED_WRAP;
 | 
						|
        else
 | 
						|
          return HandshakeStatus.NEED_UNWRAP;
 | 
						|
      }
 | 
						|
 | 
						|
    // XXX what we need to do here is generate a "stream" of handshake
 | 
						|
    // messages, and insert them into fragment amounts that we have available.
 | 
						|
    // A handshake message can span multiple records, and we can put
 | 
						|
    // multiple records into a single record.
 | 
						|
    //
 | 
						|
    // So, we can have one of two states:
 | 
						|
    //
 | 
						|
    // 1) We have enough space in the record we are creating to push out
 | 
						|
    //    everything we need to on this round. This is easy; we just
 | 
						|
    //    repeatedly fill in these messages in the buffer, so we get something
 | 
						|
    //    that looks like this:
 | 
						|
    //                 ________________________________
 | 
						|
    //       records: |________________________________|
 | 
						|
    //    handshakes: |______|__|__________|
 | 
						|
    //
 | 
						|
    // 2) We can put part of one handshake message in the current record,
 | 
						|
    //    but we must put the rest of it in the following record, or possibly
 | 
						|
    //    more than one following record. So here, we'd see this:
 | 
						|
    //
 | 
						|
    //                 ________________________
 | 
						|
    //       records: |_______|_______|________|
 | 
						|
    //    handshakes: |____|_______|_________|
 | 
						|
    //
 | 
						|
    // We *could* make this a lot easier by just only ever emitting one
 | 
						|
    // record per call, but then we would waste potentially a lot of space
 | 
						|
    // and waste a lot of TCP packets by doing it the simple way. What
 | 
						|
    // we desire here is that we *maximize* our usage of the resources
 | 
						|
    // given to us, and to use as much space in the present fragment as
 | 
						|
    // we can.
 | 
						|
    //
 | 
						|
    // Note that we pretty much have to support this, anyway, because SSL
 | 
						|
    // provides no guarantees that the record size is large enough to
 | 
						|
    // admit *even one* handshake message. Also, callers could call on us
 | 
						|
    // with a short buffer, even though they aren't supposed to.
 | 
						|
    //
 | 
						|
    // This is somewhat complicated by the fact that we don't know, a priori,
 | 
						|
    // how large a handshake message will be until we've built it, and our
 | 
						|
    // design builds the message around the byte buffer.
 | 
						|
    //
 | 
						|
    // Some ways to handle this:
 | 
						|
    //
 | 
						|
    //  1. Write our outgoing handshake messages to a private buffer,
 | 
						|
    //     big enough per message (and, if we run out of space, resize that
 | 
						|
    //     buffer) and push (possibly part of) this buffer out to the
 | 
						|
    //     outgoing buffer. This isn't that great because we'd need to
 | 
						|
    //     store and copy things unnecessarily.
 | 
						|
    //
 | 
						|
    //  2. Build outgoing handshake objects 'virtually', that is, store them
 | 
						|
    //     as collections of objects, then compute the length, and then write
 | 
						|
    //     them to a buffer, instead of making the objects views on
 | 
						|
    //     ByteBuffers for both input and output. This would complicate the
 | 
						|
    //     protocol objects a bit (although, it would amount to doing
 | 
						|
    //     separation between client objects and server objects, which is
 | 
						|
    //     pretty OK), and we still need to figure out how exactly to chunk
 | 
						|
    //     those objects across record boundaries.
 | 
						|
    //
 | 
						|
    //  3. Try to build these objects on the buffer we're given, but detect
 | 
						|
    //     when we run out of space in the output buffer, and split the
 | 
						|
    //     overflow message. This sounds like the best, but also probably
 | 
						|
    //     the hardest to code.
 | 
						|
output_loop:
 | 
						|
    while (fragment.remaining() >= 4 && state.isWriteState())
 | 
						|
      {
 | 
						|
        switch (state)
 | 
						|
          {
 | 
						|
            // Hello Request.
 | 
						|
            //
 | 
						|
            // This message is sent by the server to initiate a new
 | 
						|
            // handshake, to establish new session keys.
 | 
						|
            case WRITE_HELLO_REQUEST:
 | 
						|
            {
 | 
						|
              Handshake handshake = new Handshake(fragment);
 | 
						|
              handshake.setType(Handshake.Type.HELLO_REQUEST);
 | 
						|
              handshake.setLength(0);
 | 
						|
              fragment.position(fragment.position() + 4);
 | 
						|
              if (Debug.DEBUG)
 | 
						|
                logger.log(Component.SSL_HANDSHAKE, "{0}", handshake);
 | 
						|
              state = READ_CLIENT_HELLO;
 | 
						|
            }
 | 
						|
            break output_loop; // XXX temporary
 | 
						|
 | 
						|
            // Server Hello.
 | 
						|
            //
 | 
						|
            // This message is sent immediately following the client hello.
 | 
						|
            // It informs the client of the cipher suite, compression method,
 | 
						|
            // session ID (which may have been a continued session), and any
 | 
						|
            // supported extensions.
 | 
						|
            case WRITE_SERVER_HELLO:
 | 
						|
            {
 | 
						|
              ServerHelloBuilder hello = new ServerHelloBuilder();
 | 
						|
              hello.setVersion(engine.session().version);
 | 
						|
              Random r = hello.random();
 | 
						|
              r.setGmtUnixTime(Util.unixTime());
 | 
						|
              byte[] nonce = new byte[28];
 | 
						|
              engine.session().random().nextBytes(nonce);
 | 
						|
              r.setRandomBytes(nonce);
 | 
						|
              serverRandom = r.copy();
 | 
						|
              hello.setSessionId(engine.session().getId());
 | 
						|
              hello.setCipherSuite(engine.session().suite);
 | 
						|
              hello.setCompressionMethod(compression);
 | 
						|
              if (clientHadExtensions)
 | 
						|
                {
 | 
						|
                  // XXX figure this out.
 | 
						|
                }
 | 
						|
              else // Don't send any extensions.
 | 
						|
                hello.setDisableExtensions(true);
 | 
						|
 | 
						|
              if (Debug.DEBUG)
 | 
						|
                logger.log(Component.SSL_HANDSHAKE, "{0}", hello);
 | 
						|
 | 
						|
              int typeLen = ((Handshake.Type.SERVER_HELLO.getValue() << 24)
 | 
						|
                  | (hello.length() & 0xFFFFFF));
 | 
						|
              fragment.putInt(typeLen);
 | 
						|
 | 
						|
              outBuffer = hello.buffer();
 | 
						|
              int l = Math.min(fragment.remaining(), outBuffer.remaining());
 | 
						|
              fragment.put((ByteBuffer) outBuffer.duplicate().limit(outBuffer.position() + l));
 | 
						|
              outBuffer.position(outBuffer.position() + l);
 | 
						|
 | 
						|
              CipherSuite cs = engine.session().suite;
 | 
						|
              KeyExchangeAlgorithm kex = cs.keyExchangeAlgorithm();
 | 
						|
              if (continuedSession)
 | 
						|
                {
 | 
						|
                  byte[][] keys = generateKeys(clientRandom, serverRandom,
 | 
						|
                                               engine.session());
 | 
						|
                  setupSecurityParameters(keys, false, engine, compression);
 | 
						|
                  engine.changeCipherSpec();
 | 
						|
                  state = WRITE_FINISHED;
 | 
						|
                }
 | 
						|
              else if (kex == DHE_DSS || kex == DHE_RSA || kex == RSA
 | 
						|
                       || kex == RSA_PSK)
 | 
						|
                {
 | 
						|
                  certLoader = new CertLoader();
 | 
						|
                  tasks.add(certLoader);
 | 
						|
                  state = WRITE_CERTIFICATE;
 | 
						|
                  if (kex == DHE_DSS || kex == DHE_RSA)
 | 
						|
                    {
 | 
						|
                      genDH = new GenDH();
 | 
						|
                      tasks.add(genDH);
 | 
						|
                    }
 | 
						|
                  break output_loop;
 | 
						|
                }
 | 
						|
              else if (kex == PSK)
 | 
						|
                {
 | 
						|
                  state = WRITE_SERVER_KEY_EXCHANGE;
 | 
						|
                }
 | 
						|
              else if (kex == DHE_PSK || kex == DH_anon)
 | 
						|
                {
 | 
						|
                  genDH = new GenDH();
 | 
						|
                  tasks.add(genDH);
 | 
						|
                  state = WRITE_SERVER_KEY_EXCHANGE;
 | 
						|
                  break output_loop;
 | 
						|
                }
 | 
						|
              else if (engine.getWantClientAuth() || engine.getNeedClientAuth())
 | 
						|
                {
 | 
						|
                  state = WRITE_CERTIFICATE_REQUEST;
 | 
						|
                }
 | 
						|
              else
 | 
						|
                state = WRITE_SERVER_HELLO_DONE;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
            // Certificate.
 | 
						|
            //
 | 
						|
            // This message is sent immediately following the server hello,
 | 
						|
            // IF the cipher suite chosen requires that the server identify
 | 
						|
            // itself (usually, servers must authenticate).
 | 
						|
            case WRITE_CERTIFICATE:
 | 
						|
            {
 | 
						|
              // We must have scheduled a certificate loader to run.
 | 
						|
              assert(certLoader != null);
 | 
						|
              assert(certLoader.hasRun());
 | 
						|
              if (certLoader.thrown() != null)
 | 
						|
                throw new AlertException(new Alert(Alert.Level.FATAL,
 | 
						|
                                                   Alert.Description.HANDSHAKE_FAILURE),
 | 
						|
                                         certLoader.thrown());
 | 
						|
              java.security.cert.Certificate[] chain
 | 
						|
                = engine.session().getLocalCertificates();
 | 
						|
              CertificateBuilder cert = new CertificateBuilder(CertificateType.X509);
 | 
						|
              try
 | 
						|
                {
 | 
						|
                  cert.setCertificates(Arrays.asList(chain));
 | 
						|
                }
 | 
						|
              catch (CertificateException ce)
 | 
						|
                {
 | 
						|
                  throw new SSLException(ce);
 | 
						|
                }
 | 
						|
 | 
						|
              if (Debug.DEBUG)
 | 
						|
                {
 | 
						|
                  logger.logv(Component.SSL_HANDSHAKE, "my cert:\n{0}", localCert);
 | 
						|
                  logger.logv(Component.SSL_HANDSHAKE, "{0}", cert);
 | 
						|
                }
 | 
						|
 | 
						|
              int typeLen = ((CERTIFICATE.getValue() << 24)
 | 
						|
                             | (cert.length() & 0xFFFFFF));
 | 
						|
              fragment.putInt(typeLen);
 | 
						|
 | 
						|
              outBuffer = cert.buffer();
 | 
						|
              final int l = Math.min(fragment.remaining(), outBuffer.remaining());
 | 
						|
              fragment.put((ByteBuffer) outBuffer.duplicate().limit(outBuffer.position() + l));
 | 
						|
              outBuffer.position(outBuffer.position() + l);
 | 
						|
 | 
						|
              CipherSuite s = engine.session().suite;
 | 
						|
              KeyExchangeAlgorithm kexalg = s.keyExchangeAlgorithm();
 | 
						|
              if (kexalg == DHE_DSS || kexalg == DHE_RSA)
 | 
						|
                {
 | 
						|
                  genDH = new GenDH();
 | 
						|
                  tasks.add(genDH);
 | 
						|
                  state = WRITE_SERVER_KEY_EXCHANGE;
 | 
						|
                  break output_loop;
 | 
						|
                }
 | 
						|
              else if (kexalg == RSA_PSK)
 | 
						|
                state = WRITE_SERVER_KEY_EXCHANGE;
 | 
						|
              else if (engine.getWantClientAuth() || engine.getNeedClientAuth())
 | 
						|
                {
 | 
						|
                  state = WRITE_CERTIFICATE_REQUEST;
 | 
						|
                }
 | 
						|
              else
 | 
						|
                state = WRITE_SERVER_HELLO_DONE;
 | 
						|
            }
 | 
						|
            break output_loop; // XXX temporary
 | 
						|
 | 
						|
            // Server key exchange.
 | 
						|
            //
 | 
						|
            // This message is sent, following the certificate if sent,
 | 
						|
            // otherwise following the server hello, IF the chosen cipher
 | 
						|
            // suite requires that the server send explicit key exchange
 | 
						|
            // parameters (that is, if the key exchange parameters are not
 | 
						|
            // implicit in the server's certificate).
 | 
						|
            case WRITE_SERVER_KEY_EXCHANGE:
 | 
						|
            {
 | 
						|
              KeyExchangeAlgorithm kex = engine.session().suite.keyExchangeAlgorithm();
 | 
						|
 | 
						|
              ByteBuffer paramBuffer = null;
 | 
						|
              ByteBuffer sigBuffer = null;
 | 
						|
              if (kex == DHE_DSS || kex == DHE_RSA || kex == DH_anon
 | 
						|
                  || kex == DHE_PSK)
 | 
						|
                {
 | 
						|
                  assert(genDH != null);
 | 
						|
                  assert(genDH.hasRun());
 | 
						|
                  if (genDH.thrown() != null)
 | 
						|
                    throw new AlertException(new Alert(Alert.Level.FATAL,
 | 
						|
                                                       Alert.Description.HANDSHAKE_FAILURE),
 | 
						|
                                             genDH.thrown());
 | 
						|
                  assert(dhPair != null);
 | 
						|
                  initDiffieHellman((DHPrivateKey) dhPair.getPrivate(),
 | 
						|
                                    engine.session().random());
 | 
						|
                  paramBuffer = genDH.paramsBuffer;
 | 
						|
                  sigBuffer = genDH.sigBuffer;
 | 
						|
 | 
						|
                  if (kex == DHE_PSK)
 | 
						|
                    {
 | 
						|
                      String identityHint
 | 
						|
                        = engine.contextImpl.pskManager.chooseIdentityHint();
 | 
						|
                      ServerDHE_PSKParameters psk =
 | 
						|
                        new ServerDHE_PSKParameters(identityHint, paramBuffer);
 | 
						|
                      paramBuffer = psk.buffer();
 | 
						|
                    }
 | 
						|
                }
 | 
						|
              if (kex == RSA_PSK)
 | 
						|
                {
 | 
						|
                  String idHint = engine.contextImpl.pskManager.chooseIdentityHint();
 | 
						|
                  if (idHint != null)
 | 
						|
                    {
 | 
						|
                      ServerRSA_PSKParameters params
 | 
						|
                        = new ServerRSA_PSKParameters(idHint);
 | 
						|
                      paramBuffer = params.buffer();
 | 
						|
                    }
 | 
						|
                }
 | 
						|
              if (kex == PSK)
 | 
						|
                {
 | 
						|
                  String idHint = engine.contextImpl.pskManager.chooseIdentityHint();
 | 
						|
                  if (idHint != null)
 | 
						|
                    {
 | 
						|
                      ServerPSKParameters params
 | 
						|
                        = new ServerPSKParameters(idHint);
 | 
						|
                      paramBuffer = params.buffer();
 | 
						|
                    }
 | 
						|
                }
 | 
						|
              // XXX handle SRP
 | 
						|
 | 
						|
              if (paramBuffer != null)
 | 
						|
                {
 | 
						|
                  ServerKeyExchangeBuilder ske
 | 
						|
                    = new ServerKeyExchangeBuilder(engine.session().suite);
 | 
						|
                  ske.setParams(paramBuffer);
 | 
						|
                  if (sigBuffer != null)
 | 
						|
                    ske.setSignature(sigBuffer);
 | 
						|
 | 
						|
                  if (Debug.DEBUG)
 | 
						|
                    logger.log(Component.SSL_HANDSHAKE, "{0}", ske);
 | 
						|
 | 
						|
                  outBuffer = ske.buffer();
 | 
						|
                  int l = Math.min(fragment.remaining(), outBuffer.remaining());
 | 
						|
                  fragment.putInt((SERVER_KEY_EXCHANGE.getValue() << 24)
 | 
						|
                                  | (ske.length() & 0xFFFFFF));
 | 
						|
                  fragment.put((ByteBuffer) outBuffer.duplicate().limit
 | 
						|
                               (outBuffer.position() + l));
 | 
						|
                  outBuffer.position(outBuffer.position() + l);
 | 
						|
                }
 | 
						|
 | 
						|
              if (engine.getWantClientAuth() || engine.getNeedClientAuth())
 | 
						|
                state = WRITE_CERTIFICATE_REQUEST;
 | 
						|
              else
 | 
						|
                state = WRITE_SERVER_HELLO_DONE;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
            // Certificate Request.
 | 
						|
            //
 | 
						|
            // This message is sent when the server desires or requires
 | 
						|
            // client authentication with a certificate; if it is sent, it
 | 
						|
            // will be sent just after the Certificate or Server Key
 | 
						|
            // Exchange messages, whichever is sent. If neither of the
 | 
						|
            // above are sent, it will be the message that follows the
 | 
						|
            // server hello.
 | 
						|
            case WRITE_CERTIFICATE_REQUEST:
 | 
						|
            {
 | 
						|
              CertificateRequestBuilder req = new CertificateRequestBuilder();
 | 
						|
 | 
						|
              List<ClientCertificateType> types
 | 
						|
                = new ArrayList<ClientCertificateType>(4);
 | 
						|
              types.add(ClientCertificateType.RSA_SIGN);
 | 
						|
              types.add(ClientCertificateType.RSA_FIXED_DH);
 | 
						|
              types.add(ClientCertificateType.DSS_SIGN);
 | 
						|
              types.add(ClientCertificateType.DSS_FIXED_DH);
 | 
						|
              req.setTypes(types);
 | 
						|
 | 
						|
              X509Certificate[] anchors
 | 
						|
                = engine.contextImpl.trustManager.getAcceptedIssuers();
 | 
						|
              List<X500Principal> issuers
 | 
						|
                = new ArrayList<X500Principal>(anchors.length);
 | 
						|
              for (X509Certificate cert : anchors)
 | 
						|
                issuers.add(cert.getIssuerX500Principal());
 | 
						|
              req.setAuthorities(issuers);
 | 
						|
 | 
						|
              if (Debug.DEBUG)
 | 
						|
                logger.log(Component.SSL_HANDSHAKE, "{0}", req);
 | 
						|
 | 
						|
              fragment.putInt((CERTIFICATE_REQUEST.getValue() << 24)
 | 
						|
                              | (req.length() & 0xFFFFFF));
 | 
						|
 | 
						|
              outBuffer = req.buffer();
 | 
						|
              int l = Math.min(outBuffer.remaining(), fragment.remaining());
 | 
						|
              fragment.put((ByteBuffer) outBuffer.duplicate().limit(outBuffer.position() + l));
 | 
						|
              outBuffer.position(outBuffer.position() + l);
 | 
						|
 | 
						|
              state = WRITE_SERVER_HELLO_DONE;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
            // Server Hello Done.
 | 
						|
            //
 | 
						|
            // This message is always sent by the server, to terminate its
 | 
						|
            // side of the handshake. Since the server's handshake message
 | 
						|
            // may comprise multiple, optional messages, this sentinel
 | 
						|
            // message lets the client know when the server's message stream
 | 
						|
            // is complete.
 | 
						|
            case WRITE_SERVER_HELLO_DONE:
 | 
						|
            {
 | 
						|
              // ServerHelloDone is zero-length; just put in the type
 | 
						|
              // field.
 | 
						|
              fragment.putInt(SERVER_HELLO_DONE.getValue() << 24);
 | 
						|
              if (Debug.DEBUG)
 | 
						|
                logger.logv(Component.SSL_HANDSHAKE, "writing ServerHelloDone");
 | 
						|
              state = READ_CERTIFICATE;
 | 
						|
            }
 | 
						|
            break output_loop; // XXX temporary
 | 
						|
 | 
						|
            // Finished.
 | 
						|
            //
 | 
						|
            // This is always sent by the server to verify the keys that the
 | 
						|
            // server will use to encrypt and authenticate. In a full
 | 
						|
            // handshake, this message will be sent after the client's
 | 
						|
            // finished message; in an abbreviated handshake (with a continued
 | 
						|
            // session) the server sends its finished message first.
 | 
						|
            //
 | 
						|
            // This message follows the change cipher spec message, which is
 | 
						|
            // sent out-of-band in a different SSL content-type.
 | 
						|
            //
 | 
						|
            // This is the first message that the server will send encrypted
 | 
						|
            // and authenticated with the newly negotiated session keys.
 | 
						|
            case WRITE_FINISHED:
 | 
						|
            {
 | 
						|
              MessageDigest md5copy = null;
 | 
						|
              MessageDigest shacopy = null;
 | 
						|
              try
 | 
						|
                {
 | 
						|
                  md5copy = (MessageDigest) md5.clone();
 | 
						|
                  shacopy = (MessageDigest) sha.clone();
 | 
						|
                }
 | 
						|
              catch (CloneNotSupportedException cnse)
 | 
						|
                {
 | 
						|
                  // We're improperly configured to use a non-cloneable
 | 
						|
                  // md5/sha-1, OR there's a runtime bug.
 | 
						|
                  throw new SSLException(cnse);
 | 
						|
                }
 | 
						|
              outBuffer
 | 
						|
                = generateFinished(md5copy, shacopy, false,
 | 
						|
                                   engine.session());
 | 
						|
 | 
						|
              fragment.putInt((FINISHED.getValue() << 24)
 | 
						|
                              | outBuffer.remaining() & 0xFFFFFF);
 | 
						|
 | 
						|
              int l = Math.min(outBuffer.remaining(), fragment.remaining());
 | 
						|
              fragment.put((ByteBuffer) outBuffer.duplicate().limit(outBuffer.position() + l));
 | 
						|
              outBuffer.position(outBuffer.position() + l);
 | 
						|
 | 
						|
              if (continuedSession)
 | 
						|
                state = READ_FINISHED;
 | 
						|
              else
 | 
						|
                state = DONE;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
          }
 | 
						|
      }
 | 
						|
    if (!tasks.isEmpty())
 | 
						|
      return HandshakeStatus.NEED_TASK;
 | 
						|
    if (state.isWriteState() || outBuffer.hasRemaining())
 | 
						|
      return HandshakeStatus.NEED_WRAP;
 | 
						|
    if (state.isReadState())
 | 
						|
      return HandshakeStatus.NEED_UNWRAP;
 | 
						|
 | 
						|
    return HandshakeStatus.FINISHED;
 | 
						|
  }
 | 
						|
 | 
						|
  @Override HandshakeStatus status()
 | 
						|
  {
 | 
						|
    if (!tasks.isEmpty())
 | 
						|
      return HandshakeStatus.NEED_TASK;
 | 
						|
    if (state.isReadState())
 | 
						|
      return HandshakeStatus.NEED_UNWRAP;
 | 
						|
    if (state.isWriteState())
 | 
						|
      return HandshakeStatus.NEED_WRAP;
 | 
						|
 | 
						|
    return HandshakeStatus.FINISHED;
 | 
						|
  }
 | 
						|
 | 
						|
  @Override void checkKeyExchange() throws SSLException
 | 
						|
  {
 | 
						|
    if (continuedSession) // No key exchange needed.
 | 
						|
      return;
 | 
						|
    KeyExchangeAlgorithm kex = engine.session().suite.keyExchangeAlgorithm();
 | 
						|
    if (kex == NONE || kex == PSK || kex == RSA_PSK) // Don't need one.
 | 
						|
      return;
 | 
						|
    if (keyExchangeTask == null) // An error if we never created one.
 | 
						|
      throw new AlertException(new Alert(Alert.Level.FATAL,
 | 
						|
                                         Alert.Description.INTERNAL_ERROR));
 | 
						|
    if (!keyExchangeTask.hasRun()) // An error if the caller never ran it.
 | 
						|
      throw new AlertException(new Alert(Alert.Level.FATAL,
 | 
						|
                                         Alert.Description.INTERNAL_ERROR));
 | 
						|
    if (keyExchangeTask.thrown() != null) // An error was thrown.
 | 
						|
      throw new AlertException(new Alert(Alert.Level.FATAL,
 | 
						|
                                         Alert.Description.HANDSHAKE_FAILURE),
 | 
						|
                               keyExchangeTask.thrown());
 | 
						|
  }
 | 
						|
 | 
						|
  @Override void handleV2Hello(ByteBuffer hello)
 | 
						|
  {
 | 
						|
    int len = hello.getShort(0) & 0x7FFF;
 | 
						|
    md5.update((ByteBuffer) hello.duplicate().position(2).limit(len+2));
 | 
						|
    sha.update((ByteBuffer) hello.duplicate().position(2).limit(len+2));
 | 
						|
    helloV2 = true;
 | 
						|
  }
 | 
						|
 | 
						|
  private ByteBuffer signParams(ByteBuffer serverParams)
 | 
						|
    throws NoSuchAlgorithmException, InvalidKeyException, SignatureException
 | 
						|
  {
 | 
						|
    SignatureAlgorithm alg = engine.session().suite.signatureAlgorithm();
 | 
						|
    java.security.Signature sig
 | 
						|
      = java.security.Signature.getInstance(alg.algorithm());
 | 
						|
    PrivateKey key = engine.contextImpl.keyManager.getPrivateKey(keyAlias);
 | 
						|
    if (Debug.DEBUG_KEY_EXCHANGE)
 | 
						|
      logger.logv(Component.SSL_HANDSHAKE, "server key: {0}", key);
 | 
						|
    sig.initSign(key);
 | 
						|
    sig.update(clientRandom.buffer());
 | 
						|
    sig.update(serverRandom.buffer());
 | 
						|
    sig.update(serverParams);
 | 
						|
    byte[] sigVal = sig.sign();
 | 
						|
    Signature signature = new Signature(sigVal, engine.session().suite.signatureAlgorithm());
 | 
						|
    return signature.buffer();
 | 
						|
  }
 | 
						|
 | 
						|
  private void verifyClient(byte[] sigValue) throws SSLException, SignatureException
 | 
						|
  {
 | 
						|
    MessageDigest md5copy = null;
 | 
						|
    MessageDigest shacopy = null;
 | 
						|
    try
 | 
						|
      {
 | 
						|
        md5copy = (MessageDigest) md5.clone();
 | 
						|
        shacopy = (MessageDigest) sha.clone();
 | 
						|
      }
 | 
						|
    catch (CloneNotSupportedException cnse)
 | 
						|
      {
 | 
						|
        // Mis-configured with non-cloneable digests.
 | 
						|
        throw new SSLException(cnse);
 | 
						|
      }
 | 
						|
    byte[] toSign = null;
 | 
						|
    if (engine.session().version == ProtocolVersion.SSL_3)
 | 
						|
      toSign = genV3CertificateVerify(md5copy, shacopy, engine.session());
 | 
						|
    else
 | 
						|
      {
 | 
						|
        if (engine.session().suite.signatureAlgorithm() == SignatureAlgorithm.RSA)
 | 
						|
          toSign = Util.concat(md5copy.digest(), shacopy.digest());
 | 
						|
        else
 | 
						|
          toSign = shacopy.digest();
 | 
						|
      }
 | 
						|
 | 
						|
    try
 | 
						|
      {
 | 
						|
        java.security.Signature sig = java.security.Signature.getInstance(engine.session().suite.signatureAlgorithm().toString());
 | 
						|
        sig.initVerify(clientCert);
 | 
						|
        sig.update(toSign);
 | 
						|
        sig.verify(sigValue);
 | 
						|
      }
 | 
						|
    catch (InvalidKeyException ike)
 | 
						|
      {
 | 
						|
        throw new SSLException(ike);
 | 
						|
      }
 | 
						|
    catch (NoSuchAlgorithmException nsae)
 | 
						|
      {
 | 
						|
        throw new SSLException(nsae);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  // Delegated tasks.
 | 
						|
 | 
						|
  class CertLoader extends DelegatedTask
 | 
						|
  {
 | 
						|
    CertLoader()
 | 
						|
    {
 | 
						|
    }
 | 
						|
 | 
						|
    public void implRun() throws SSLException
 | 
						|
    {
 | 
						|
      KeyExchangeAlgorithm kexalg = engine.session().suite.keyExchangeAlgorithm();
 | 
						|
      X509ExtendedKeyManager km = engine.contextImpl.keyManager;
 | 
						|
      Principal[] issuers = null; // XXX use TrustedAuthorities extension.
 | 
						|
      keyAlias = km.chooseEngineServerAlias(kexalg.name(), issuers, engine);
 | 
						|
      if (keyAlias == null)
 | 
						|
        throw new SSLException("no certificates available");
 | 
						|
      X509Certificate[] chain = km.getCertificateChain(keyAlias);
 | 
						|
      engine.session().setLocalCertificates(chain);
 | 
						|
      localCert = chain[0];
 | 
						|
      serverKey = km.getPrivateKey(keyAlias);
 | 
						|
      if (kexalg == DH_DSS || kexalg == DH_RSA)
 | 
						|
        dhPair = new KeyPair(localCert.getPublicKey(),
 | 
						|
                             km.getPrivateKey(keyAlias));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Delegated task for generating Diffie-Hellman parameters.
 | 
						|
   */
 | 
						|
  private class GenDH extends DelegatedTask
 | 
						|
  {
 | 
						|
    ByteBuffer paramsBuffer;
 | 
						|
    ByteBuffer sigBuffer;
 | 
						|
 | 
						|
    protected void implRun()
 | 
						|
      throws NoSuchAlgorithmException, InvalidAlgorithmParameterException,
 | 
						|
             InvalidKeyException, SignatureException
 | 
						|
    {
 | 
						|
      KeyPairGenerator dhGen = KeyPairGenerator.getInstance("DH");
 | 
						|
      DHParameterSpec dhparams = DiffieHellman.getParams().getParams();
 | 
						|
      dhGen.initialize(dhparams, engine.session().random());
 | 
						|
      dhPair = dhGen.generateKeyPair();
 | 
						|
      DHPublicKey pub = (DHPublicKey) dhPair.getPublic();
 | 
						|
 | 
						|
      // Generate the parameters message.
 | 
						|
      ServerDHParams params = new ServerDHParams(pub.getParams().getP(),
 | 
						|
                                                 pub.getParams().getG(),
 | 
						|
                                                 pub.getY());
 | 
						|
      paramsBuffer = params.buffer();
 | 
						|
 | 
						|
      // Sign the parameters, if needed.
 | 
						|
      if (engine.session().suite.signatureAlgorithm() != SignatureAlgorithm.ANONYMOUS)
 | 
						|
        {
 | 
						|
          sigBuffer = signParams(paramsBuffer);
 | 
						|
          paramsBuffer.rewind();
 | 
						|
        }
 | 
						|
      if (Debug.DEBUG_KEY_EXCHANGE)
 | 
						|
        logger.logv(Component.SSL_KEY_EXCHANGE,
 | 
						|
                    "Diffie-Hellman public:{0} private:{1}",
 | 
						|
                    dhPair.getPublic(), dhPair.getPrivate());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  class RSAKeyExchange extends DelegatedTask
 | 
						|
  {
 | 
						|
    private final byte[] encryptedPreMasterSecret;
 | 
						|
 | 
						|
    RSAKeyExchange(byte[] encryptedPreMasterSecret)
 | 
						|
    {
 | 
						|
      this.encryptedPreMasterSecret = encryptedPreMasterSecret;
 | 
						|
    }
 | 
						|
 | 
						|
    public void implRun()
 | 
						|
      throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException,
 | 
						|
             NoSuchAlgorithmException, NoSuchPaddingException, SSLException
 | 
						|
    {
 | 
						|
      Cipher rsa = Cipher.getInstance("RSA");
 | 
						|
      rsa.init(Cipher.DECRYPT_MODE, serverKey);
 | 
						|
      rsa.init(Cipher.DECRYPT_MODE, localCert);
 | 
						|
      preMasterSecret = rsa.doFinal(encryptedPreMasterSecret);
 | 
						|
      generateMasterSecret(clientRandom, serverRandom, engine.session());
 | 
						|
      byte[][] keys = generateKeys(clientRandom, serverRandom, engine.session());
 | 
						|
      setupSecurityParameters(keys, false, engine, compression);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  class RSA_PSKExchange extends DelegatedTask
 | 
						|
  {
 | 
						|
    private final byte[] encryptedPreMasterSecret;
 | 
						|
    private final SecretKey psKey;
 | 
						|
 | 
						|
    RSA_PSKExchange(byte[] encryptedPreMasterSecret, SecretKey psKey)
 | 
						|
    {
 | 
						|
      this.encryptedPreMasterSecret = encryptedPreMasterSecret;
 | 
						|
      this.psKey = psKey;
 | 
						|
    }
 | 
						|
 | 
						|
    public @Override void implRun()
 | 
						|
      throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException,
 | 
						|
             NoSuchAlgorithmException, NoSuchPaddingException, SSLException
 | 
						|
    {
 | 
						|
      Cipher rsa = Cipher.getInstance("RSA");
 | 
						|
      rsa.init(Cipher.DECRYPT_MODE, serverKey);
 | 
						|
      rsa.init(Cipher.DECRYPT_MODE, localCert);
 | 
						|
      byte[] rsaSecret = rsa.doFinal(encryptedPreMasterSecret);
 | 
						|
      byte[] psSecret = psKey.getEncoded();
 | 
						|
      preMasterSecret = new byte[rsaSecret.length + psSecret.length + 4];
 | 
						|
      preMasterSecret[0] = (byte) (rsaSecret.length >>> 8);
 | 
						|
      preMasterSecret[1] = (byte)  rsaSecret.length;
 | 
						|
      System.arraycopy(rsaSecret, 0, preMasterSecret, 2, rsaSecret.length);
 | 
						|
      preMasterSecret[rsaSecret.length + 2] = (byte) (psSecret.length >>> 8);
 | 
						|
      preMasterSecret[rsaSecret.length + 3] = (byte)  psSecret.length;
 | 
						|
      System.arraycopy(psSecret, 0, preMasterSecret, rsaSecret.length+4,
 | 
						|
                       psSecret.length);
 | 
						|
 | 
						|
      generateMasterSecret(clientRandom, serverRandom, engine.session());
 | 
						|
      byte[][] keys = generateKeys(clientRandom, serverRandom, engine.session());
 | 
						|
      setupSecurityParameters(keys, false, engine, compression);
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 |