| /* 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(); |
| } |
| } |
| |
| public boolean isClosed() |
| { |
| if (underlyingSocket != null) |
| { |
| return underlyingSocket.isClosed(); |
| } |
| else |
| { |
| return super.isClosed(); |
| } |
| } |
| |
| 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); |
| } |
| } |
| |
| public boolean getOOBInline() throws SocketException |
| { |
| if (underlyingSocket != null) |
| { |
| return underlyingSocket.getOOBInline(); |
| } |
| else |
| { |
| return super.getOOBInline(); |
| } |
| } |
| |
| public void setKeepAlive(boolean flag) throws SocketException |
| { |
| if (underlyingSocket != null) |
| { |
| underlyingSocket.setKeepAlive(flag); |
| } |
| else |
| { |
| super.setKeepAlive(flag); |
| } |
| } |
| |
| public boolean getKeepAlive() throws SocketException |
| { |
| if (underlyingSocket != null) |
| { |
| return underlyingSocket.getKeepAlive(); |
| } |
| else |
| { |
| return super.getKeepAlive(); |
| } |
| } |
| |
| public void setTrafficClass(int clazz) throws SocketException |
| { |
| if (underlyingSocket != null) |
| { |
| underlyingSocket.setTrafficClass(clazz); |
| } |
| else |
| { |
| super.setTrafficClass(clazz); |
| } |
| } |
| |
| public int getTrafficClass() throws SocketException |
| { |
| if (underlyingSocket != null) |
| { |
| return underlyingSocket.getTrafficClass(); |
| } |
| else |
| { |
| return super.getTrafficClass(); |
| } |
| } |
| |
| public void setReuseAddress(boolean flag) throws SocketException |
| { |
| if (underlyingSocket != null) |
| { |
| underlyingSocket.setReuseAddress(flag); |
| } |
| else |
| { |
| super.setReuseAddress(flag); |
| } |
| } |
| |
| public boolean getReuseAddress() throws SocketException |
| { |
| if (underlyingSocket != null) |
| { |
| return underlyingSocket.getReuseAddress(); |
| } |
| else |
| { |
| return super.getReuseAddress(); |
| } |
| } |
| |
| public void shutdownInput() throws IOException |
| { |
| if (underlyingSocket != null) |
| { |
| underlyingSocket.shutdownInput(); |
| } |
| else |
| { |
| super.shutdownInput(); |
| } |
| } |
| |
| public void shutdownOutput() throws IOException |
| { |
| if (underlyingSocket != null) |
| { |
| underlyingSocket.shutdownOutput(); |
| } |
| else |
| { |
| super.shutdownOutput(); |
| } |
| } |
| |
| public boolean isConnected() |
| { |
| if (underlyingSocket != null) |
| { |
| return underlyingSocket.isConnected(); |
| } |
| else |
| { |
| return super.isConnected(); |
| } |
| } |
| |
| public boolean isInputShutdown() |
| { |
| if (underlyingSocket != null) |
| { |
| return underlyingSocket.isInputShutdown(); |
| } |
| else |
| { |
| return super.isInputShutdown(); |
| } |
| } |
| |
| public boolean isOutputShutdown() |
| { |
| if (underlyingSocket != null) |
| { |
| return underlyingSocket.isOutputShutdown(); |
| } |
| else |
| { |
| return super.isOutputShutdown(); |
| } |
| } |
| |
| 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(); |
| } |
| } |
| } |