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