summaryrefslogtreecommitdiff
path: root/gnu/javax/net/ssl/provider/AbstractHandshake.java
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/javax/net/ssl/provider/AbstractHandshake.java')
-rw-r--r--gnu/javax/net/ssl/provider/AbstractHandshake.java1205
1 files changed, 1205 insertions, 0 deletions
diff --git a/gnu/javax/net/ssl/provider/AbstractHandshake.java b/gnu/javax/net/ssl/provider/AbstractHandshake.java
new file mode 100644
index 000000000..d80a5bb78
--- /dev/null
+++ b/gnu/javax/net/ssl/provider/AbstractHandshake.java
@@ -0,0 +1,1205 @@
+/* AbstractHandshake.java -- abstract handshake handler.
+ 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 gnu.classpath.debug.Component;
+import gnu.classpath.debug.SystemLogger;
+import gnu.java.security.action.GetSecurityPropertyAction;
+import gnu.java.security.prng.IRandom;
+import gnu.java.security.prng.LimitReachedException;
+import gnu.java.security.util.ByteArray;
+import gnu.javax.security.auth.callback.CertificateCallback;
+import gnu.javax.security.auth.callback.DefaultCallbackHandler;
+
+import java.nio.ByteBuffer;
+import java.security.AccessController;
+import java.security.DigestException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyManagementException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivilegedExceptionAction;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.X509TrustManager;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.ConfirmationCallback;
+
+/**
+ * The base interface for handshake implementations. Concrete
+ * subclasses of this class (one for the server, one for the client)
+ * handle the HANDSHAKE content-type in communications.
+ */
+public abstract class AbstractHandshake
+{
+ protected static final SystemLogger logger = SystemLogger.SYSTEM;
+
+ /**
+ * "server finished" -- TLS 1.0 and later
+ */
+ protected static final byte[] SERVER_FINISHED
+ = new byte[] {
+ 115, 101, 114, 118, 101, 114, 32, 102, 105, 110, 105, 115,
+ 104, 101, 100
+ };
+
+ /**
+ * "client finished" -- TLS 1.0 and later
+ */
+ protected static final byte[] CLIENT_FINISHED
+ = new byte[] {
+ 99, 108, 105, 101, 110, 116, 32, 102, 105, 110, 105, 115,
+ 104, 101, 100
+ };
+
+ /**
+ * "key expansion" -- TLS 1.0 and later
+ */
+ private static final byte[] KEY_EXPANSION =
+ new byte[] { 107, 101, 121, 32, 101, 120, 112,
+ 97, 110, 115, 105, 111, 110 };
+
+ /**
+ * "master secret" -- TLS 1.0 and later
+ */
+ private static final byte[] MASTER_SECRET
+ = new byte[] {
+ 109, 97, 115, 116, 101, 114, 32, 115, 101, 99, 114, 101, 116
+ };
+
+ /**
+ * "client write key" -- TLS 1.0 exportable whitener.
+ */
+ private static final byte[] CLIENT_WRITE_KEY
+ = new byte[] {
+ 99, 108, 105, 101, 110, 116, 32, 119, 114, 105, 116, 101, 32, 107,
+ 101, 121
+ };
+
+ /**
+ * "server write key" -- TLS 1.0 exportable whitener.
+ */
+ private static final byte[] SERVER_WRITE_KEY
+ = new byte[] {
+ 115, 101, 114, 118, 101, 114, 32, 119, 114, 105, 116, 101, 32, 107,
+ 101, 121
+ };
+
+ private static final byte[] IV_BLOCK
+ = new byte[] {
+ 73, 86, 32, 98, 108, 111, 99, 107
+ };
+
+ /**
+ * SSL 3.0; the string "CLNT"
+ */
+ private static final byte[] SENDER_CLIENT
+ = new byte[] { 0x43, 0x4C, 0x4E, 0x54 };
+
+ /**
+ * SSL 3.0; the string "SRVR"
+ */
+ private static final byte[] SENDER_SERVER
+ = new byte[] { 0x53, 0x52, 0x56, 0x52 };
+
+ /**
+ * SSL 3.0; the value 0x36 40 (for SHA-1 hashes) or 48 (for MD5 hashes)
+ * times.
+ */
+ protected static final byte[] PAD1 = new byte[48];
+
+ /**
+ * SSL 3.0; the value 0x5c 40 (for SHA-1 hashes) or 48 (for MD5 hashes)
+ * times.
+ */
+ protected static final byte[] PAD2 = new byte[48];
+
+ static
+ {
+ Arrays.fill(PAD1, SSLHMac.PAD1);
+ Arrays.fill(PAD2, SSLHMac.PAD2);
+ }
+
+ /**
+ * The currently-read handshake messages. There may be zero, or
+ * multiple, handshake messages in this buffer.
+ */
+ protected ByteBuffer handshakeBuffer;
+
+ /**
+ * The offset into `handshakeBuffer' where the first unread
+ * handshake message resides.
+ */
+ protected int handshakeOffset;
+
+ protected MessageDigest sha;
+ protected MessageDigest md5;
+
+ protected final SSLEngineImpl engine;
+ protected KeyAgreement keyAgreement;
+ protected byte[] preMasterSecret;
+ protected InputSecurityParameters inParams;
+ protected OutputSecurityParameters outParams;
+ protected LinkedList<DelegatedTask> tasks;
+ protected Random serverRandom;
+ protected Random clientRandom;
+ protected CompressionMethod compression;
+
+ protected AbstractHandshake(SSLEngineImpl engine)
+ throws NoSuchAlgorithmException
+ {
+ this.engine = engine;
+ sha = MessageDigest.getInstance("SHA-1");
+ md5 = MessageDigest.getInstance("MD5");
+ tasks = new LinkedList<DelegatedTask>();
+ }
+
+ /**
+ * Handles the next input message in the handshake. This is called
+ * in response to a call to {@link javax.net.ssl.SSLEngine#unwrap}
+ * for a message with content-type HANDSHAKE.
+ *
+ * @param record The input record. The callee should not assume that
+ * the record's buffer is writable, and should not try to use it for
+ * output or temporary storage.
+ * @return An {@link SSLEngineResult} describing the result.
+ */
+ public final HandshakeStatus handleInput (ByteBuffer fragment)
+ throws SSLException
+ {
+ if (!tasks.isEmpty())
+ return HandshakeStatus.NEED_TASK;
+
+ HandshakeStatus status = status();
+ if (status != HandshakeStatus.NEED_UNWRAP)
+ return status;
+
+ // Try to read another...
+ if (!pollHandshake(fragment))
+ return HandshakeStatus.NEED_UNWRAP;
+
+ while (hasMessage() && status != HandshakeStatus.NEED_WRAP)
+ {
+ int pos = handshakeOffset;
+ status = implHandleInput();
+ int len = handshakeOffset - pos;
+ if (len == 0)
+ {
+ // Don't bother; the impl is just telling us to go around
+ // again.
+ continue;
+ }
+ if (doHash())
+ {
+ if (Debug.DEBUG)
+ logger.logv(Component.SSL_HANDSHAKE, "hashing output\n{0}",
+ Util.hexDump((ByteBuffer) handshakeBuffer
+ .duplicate().position(pos)
+ .limit(pos+len), " >> "));
+ sha.update((ByteBuffer) handshakeBuffer.duplicate()
+ .position(pos).limit(pos+len));
+ md5.update((ByteBuffer) handshakeBuffer.duplicate()
+ .position(pos).limit(pos+len));
+ }
+ }
+ return status;
+ }
+
+ /**
+ * Called to process more handshake data. This method will be called
+ * repeatedly while there is remaining handshake data, and while the
+ * status is
+ * @return
+ * @throws SSLException
+ */
+ protected abstract HandshakeStatus implHandleInput()
+ throws SSLException;
+
+ /**
+ * Produce more handshake output. This is called in response to a
+ * call to {@link javax.net.ssl.SSLEngine#wrap}, when the handshake
+ * is still in progress.
+ *
+ * @param record The output record; the callee should put its output
+ * handshake message (or a part of it) in the argument's
+ * <code>fragment</code>, and should set the record length
+ * appropriately.
+ * @return An {@link SSLEngineResult} describing the result.
+ */
+ public final HandshakeStatus handleOutput (ByteBuffer fragment)
+ throws SSLException
+ {
+ if (!tasks.isEmpty())
+ return HandshakeStatus.NEED_TASK;
+
+ int orig = fragment.position();
+ SSLEngineResult.HandshakeStatus status = implHandleOutput(fragment);
+ if (doHash())
+ {
+ if (Debug.DEBUG)
+ logger.logv(Component.SSL_HANDSHAKE, "hashing output:\n{0}",
+ Util.hexDump((ByteBuffer) fragment.duplicate().flip().position(orig), " >> "));
+ sha.update((ByteBuffer) fragment.duplicate().flip().position(orig));
+ md5.update((ByteBuffer) fragment.duplicate().flip().position(orig));
+ }
+ return status;
+ }
+
+ /**
+ * Called to implement the underlying output handling. The callee should
+ * attempt to fill the given buffer as much as it can; this can include
+ * multiple, and even partial, handshake messages.
+ *
+ * @param fragment The buffer the callee should write handshake messages to.
+ * @return The new status of the handshake.
+ * @throws SSLException If an error occurs processing the output message.
+ */
+ protected abstract SSLEngineResult.HandshakeStatus implHandleOutput (ByteBuffer fragment)
+ throws SSLException;
+
+ /**
+ * Return a new instance of input security parameters, initialized with
+ * the session key. It is, of course, only valid to invoke this method
+ * once the handshake is complete, and the session keys established.
+ *
+ * <p>In the presence of a well-behaving peer, this should be called once
+ * the <code>ChangeCipherSpec</code> message is recieved.
+ *
+ * @return The input parameters for the newly established session.
+ * @throws SSLException If the handshake is not complete.
+ */
+ final InputSecurityParameters getInputParams() throws SSLException
+ {
+ checkKeyExchange();
+ return inParams;
+ }
+
+ /**
+ * Return a new instance of output security parameters, initialized with
+ * the session key. This should be called after the
+ * <code>ChangeCipherSpec</code> message is sent to the peer.
+ *
+ * @return The output parameters for the newly established session.
+ * @throws SSLException If the handshake is not complete.
+ */
+ final OutputSecurityParameters getOutputParams() throws SSLException
+ {
+ checkKeyExchange();
+ return outParams;
+ }
+
+ /**
+ * Fetch a delegated task waiting to run, if any.
+ *
+ * @return The task.
+ */
+ final Runnable getTask()
+ {
+ if (tasks.isEmpty())
+ return null;
+ return tasks.removeFirst();
+ }
+
+ /**
+ * Used by the skeletal code to query the current status of the handshake.
+ * This <em>should</em> be the same value as returned by the previous call
+ * to {@link #implHandleOutput(ByteBuffer)} or {@link
+ * #implHandleInput(ByteBuffer)}.
+ *
+ * @return The current handshake status.
+ */
+ abstract HandshakeStatus status();
+
+ /**
+ * Check if the key exchange completed successfully, throwing an exception
+ * if not.
+ *
+ * <p>Note that we assume that the caller of our SSLEngine is correct, and
+ * that they did run the delegated tasks that encapsulate the key exchange.
+ * What we are primarily checking, therefore, is that no error occurred in the
+ * key exchange operation itself.
+ *
+ * @throws SSLException If the key exchange did not complete successfully.
+ */
+ abstract void checkKeyExchange() throws SSLException;
+
+ /**
+ * Handle an SSLv2 client hello. This is only used by SSL servers.
+ *
+ * @param hello The hello message.
+ */
+ abstract void handleV2Hello(ByteBuffer hello) throws SSLException;
+
+ /**
+ * Attempt to read the next handshake message from the given
+ * record. If only a partial handshake message is available, then
+ * this method saves the incoming bytes and returns false. If a
+ * complete handshake is read, or if there was one buffered in the
+ * handshake buffer, this method returns true, and `handshakeBuffer'
+ * can be used to read the handshake.
+ *
+ * @param record The input record.
+ * @return True if a complete handshake is present in the buffer;
+ * false if only a partial one.
+ */
+ protected boolean pollHandshake (final ByteBuffer fragment)
+ {
+ // Allocate space for the new fragment.
+ if (handshakeBuffer == null
+ || handshakeBuffer.remaining() < fragment.remaining())
+ {
+ // We need space for anything still unread in the handshake
+ // buffer...
+ int len = ((handshakeBuffer == null) ? 0
+ : handshakeBuffer.position() - handshakeOffset);
+
+ // Plus room for the incoming record.
+ len += fragment.remaining();
+ reallocateBuffer(len);
+ }
+
+ if (Debug.DEBUG)
+ logger.logv(Component.SSL_HANDSHAKE, "inserting {0} into {1}",
+ fragment, handshakeBuffer);
+
+ // Put the fragment into the buffer.
+ handshakeBuffer.put(fragment);
+
+ return hasMessage();
+ }
+
+ protected boolean doHash()
+ {
+ return true;
+ }
+
+ /**
+ * Tell if the handshake buffer currently has a full handshake
+ * message.
+ */
+ protected boolean hasMessage()
+ {
+ if (handshakeBuffer == null)
+ return false;
+ ByteBuffer tmp = handshakeBuffer.duplicate();
+ tmp.flip();
+ tmp.position(handshakeOffset);
+ if (Debug.DEBUG)
+ logger.logv(Component.SSL_HANDSHAKE, "current buffer: {0}; test buffer {1}",
+ handshakeBuffer, tmp);
+ if (tmp.remaining() < 4)
+ return false;
+ Handshake handshake = new Handshake(tmp.slice());
+ if (Debug.DEBUG)
+ logger.logv(Component.SSL_HANDSHAKE, "handshake len:{0} remaining:{1}",
+ handshake.length(), tmp.remaining());
+ return (handshake.length() <= tmp.remaining() - 4);
+ }
+
+ /**
+ * Reallocate the handshake buffer so it can hold `totalLen'
+ * bytes. The smallest buffer allocated is 1024 bytes, and the size
+ * doubles from there until the buffer is sufficiently large.
+ */
+ private void reallocateBuffer (final int totalLen)
+ {
+ int len = handshakeBuffer == null ? -1
+ : handshakeBuffer.capacity() - (handshakeBuffer.limit() - handshakeOffset);
+ if (len >= totalLen)
+ {
+ // Big enough; no need to reallocate; but maybe shift the contents
+ // down.
+ if (handshakeOffset > 0)
+ {
+ handshakeBuffer.flip().position(handshakeOffset);
+ handshakeBuffer.compact();
+ handshakeOffset = 0;
+ }
+ return;
+ }
+
+ // Start at 1K (probably the system's page size). Double the size
+ // from there.
+ len = 1024;
+ while (len < totalLen)
+ len = len << 1;
+ ByteBuffer newBuf = ByteBuffer.allocate (len);
+
+ // Copy the unread bytes from the old buffer.
+ if (handshakeBuffer != null)
+ {
+ handshakeBuffer.flip ();
+ handshakeBuffer.position(handshakeOffset);
+ newBuf.put(handshakeBuffer);
+ }
+ handshakeBuffer = newBuf;
+
+ // We just put only unread handshake messages in the new buffer;
+ // the offset of the next one is now zero.
+ handshakeOffset = 0;
+ }
+
+ /**
+ * Generate a certificate verify message for SSLv3. In SSLv3, a different
+ * algorithm was used to generate this value was subtly different than
+ * that used in TLSv1.0 and later. In TLSv1.0 and later, this value is
+ * just the digest over the handshake messages.
+ *
+ * <p>SSLv3 uses the algorithm:
+ *
+ * <pre>
+CertificateVerify.signature.md5_hash
+ MD5(master_secret + pad_2 +
+ MD5(handshake_messages + master_secret + pad_1));
+Certificate.signature.sha_hash
+ SHA(master_secret + pad_2 +
+ SHA(handshake_messages + master_secret + pad_1));</pre>
+ *
+ * @param md5 The running MD5 hash of the handshake.
+ * @param sha The running SHA-1 hash of the handshake.
+ * @param session The current session being negotiated.
+ * @return The computed to-be-signed value.
+ */
+ protected byte[] genV3CertificateVerify(MessageDigest md5,
+ MessageDigest sha,
+ SessionImpl session)
+ {
+ byte[] md5value = null;
+ if (session.suite.signatureAlgorithm() == SignatureAlgorithm.RSA)
+ {
+ md5.update(session.privateData.masterSecret);
+ md5.update(PAD1, 0, 48);
+ byte[] tmp = md5.digest();
+ md5.reset();
+ md5.update(session.privateData.masterSecret);
+ md5.update(PAD2, 0, 48);
+ md5.update(tmp);
+ md5value = md5.digest();
+ }
+
+ sha.update(session.privateData.masterSecret);
+ sha.update(PAD1, 0, 40);
+ byte[] tmp = sha.digest();
+ sha.reset();
+ sha.update(session.privateData.masterSecret);
+ sha.update(PAD2, 0, 40);
+ sha.update(tmp);
+ byte[] shavalue = sha.digest();
+
+ if (md5value != null)
+ return Util.concat(md5value, shavalue);
+
+ return shavalue;
+ }
+
+ /**
+ * Generate the session keys from the computed master secret.
+ *
+ * @param clientRandom The client's nonce.
+ * @param serverRandom The server's nonce.
+ * @param session The session being established.
+ * @return The derived keys.
+ */
+ protected byte[][] generateKeys(Random clientRandom, Random serverRandom,
+ SessionImpl session)
+ {
+ int maclen = 20; // SHA-1.
+ if (session.suite.macAlgorithm() == MacAlgorithm.MD5)
+ maclen = 16;
+ int ivlen = 0;
+ if (session.suite.cipherAlgorithm() == CipherAlgorithm.DES
+ || session.suite.cipherAlgorithm() == CipherAlgorithm.DESede)
+ ivlen = 8;
+ if (session.suite.cipherAlgorithm() == CipherAlgorithm.AES)
+ ivlen = 16;
+ int keylen = session.suite.keyLength();
+
+ byte[][] keys = new byte[6][];
+ keys[0] = new byte[maclen]; // client_write_MAC_secret
+ keys[1] = new byte[maclen]; // server_write_MAC_secret
+ keys[2] = new byte[keylen]; // client_write_key
+ keys[3] = new byte[keylen]; // server_write_key
+ keys[4] = new byte[ivlen]; // client_write_iv
+ keys[5] = new byte[ivlen]; // server_write_iv
+
+ IRandom prf = null;
+ if (session.version == ProtocolVersion.SSL_3)
+ {
+ byte[] seed = new byte[clientRandom.length()
+ + serverRandom.length()];
+ serverRandom.buffer().get(seed, 0, serverRandom.length());
+ clientRandom.buffer().get(seed, serverRandom.length(),
+ clientRandom.length());
+ prf = new SSLRandom();
+ HashMap<String,byte[]> attr = new HashMap<String,byte[]>(2);
+ attr.put(SSLRandom.SECRET, session.privateData.masterSecret);
+ attr.put(SSLRandom.SEED, seed);
+ prf.init(attr);
+ }
+ else
+ {
+ byte[] seed = new byte[KEY_EXPANSION.length
+ + clientRandom.length()
+ + serverRandom.length()];
+ System.arraycopy(KEY_EXPANSION, 0, seed, 0, KEY_EXPANSION.length);
+ serverRandom.buffer().get(seed, KEY_EXPANSION.length,
+ serverRandom.length());
+ clientRandom.buffer().get(seed, (KEY_EXPANSION.length
+ + serverRandom.length()),
+ clientRandom.length());
+
+ prf = new TLSRandom();
+ HashMap<String,byte[]> attr = new HashMap<String,byte[]>(2);
+ attr.put(TLSRandom.SECRET, session.privateData.masterSecret);
+ attr.put(TLSRandom.SEED, seed);
+ prf.init(attr);
+ }
+
+ try
+ {
+ prf.nextBytes(keys[0], 0, keys[0].length);
+ prf.nextBytes(keys[1], 0, keys[1].length);
+ prf.nextBytes(keys[2], 0, keys[2].length);
+ prf.nextBytes(keys[3], 0, keys[3].length);
+
+ if (session.suite.isExportable())
+ {
+ if (session.version == ProtocolVersion.SSL_3)
+ {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ md5.update(clientRandom.buffer());
+ md5.update(serverRandom.buffer());
+ byte[] d = md5.digest();
+ System.arraycopy(d, 0, keys[4], 0, keys[4].length);
+
+ md5.reset();
+ md5.update(serverRandom.buffer());
+ md5.update(clientRandom.buffer());
+ d = md5.digest();
+ System.arraycopy(d, 0, keys[5], 0, keys[5].length);
+
+ md5.reset();
+ md5.update(keys[2]);
+ md5.update(clientRandom.buffer());
+ md5.update(serverRandom.buffer());
+ keys[2] = Util.trim(md5.digest(), 8);
+
+ md5.reset();
+ md5.update(keys[3]);
+ md5.update(serverRandom.buffer());
+ md5.update(clientRandom.buffer());
+ keys[3] = Util.trim(md5.digest(), 8);
+ }
+ else
+ {
+ TLSRandom prf2 = new TLSRandom();
+ HashMap<String,byte[]> attr = new HashMap<String,byte[]>(2);
+ attr.put(TLSRandom.SECRET, keys[2]);
+ byte[] seed = new byte[CLIENT_WRITE_KEY.length +
+ clientRandom.length() +
+ serverRandom.length()];
+ System.arraycopy(CLIENT_WRITE_KEY, 0, seed, 0,
+ CLIENT_WRITE_KEY.length);
+ clientRandom.buffer().get(seed, CLIENT_WRITE_KEY.length,
+ clientRandom.length());
+ serverRandom.buffer().get(seed, CLIENT_WRITE_KEY.length
+ + clientRandom.length(),
+ serverRandom.length());
+ attr.put(TLSRandom.SEED, seed);
+ prf2.init(attr);
+ keys[2] = new byte[8];
+ prf2.nextBytes(keys[2], 0, keys[2].length);
+
+ attr.put(TLSRandom.SECRET, keys[3]);
+ seed = new byte[SERVER_WRITE_KEY.length +
+ serverRandom.length() +
+ clientRandom.length()];
+ System.arraycopy(SERVER_WRITE_KEY, 0, seed, 0,
+ SERVER_WRITE_KEY.length);
+ serverRandom.buffer().get(seed, SERVER_WRITE_KEY.length,
+ serverRandom.length());
+ clientRandom.buffer().get(seed, SERVER_WRITE_KEY.length
+ + serverRandom.length(),
+ + clientRandom.length());
+ attr.put(TLSRandom.SEED, seed);
+ prf2.init(attr);
+ keys[3] = new byte[8];
+ prf2.nextBytes(keys[3], 0, keys[3].length);
+
+ attr.put(TLSRandom.SECRET, new byte[0]);
+ seed = new byte[IV_BLOCK.length +
+ clientRandom.length() +
+ serverRandom.length()];
+ System.arraycopy(IV_BLOCK, 0, seed, 0, IV_BLOCK.length);
+ clientRandom.buffer().get(seed, IV_BLOCK.length,
+ clientRandom.length());
+ serverRandom.buffer().get(seed, IV_BLOCK.length
+ + clientRandom.length(),
+ serverRandom.length());
+ attr.put(TLSRandom.SEED, seed);
+ prf2.init(attr);
+ prf2.nextBytes(keys[4], 0, keys[4].length);
+ prf2.nextBytes(keys[5], 0, keys[5].length);
+ }
+ }
+ else
+ {
+ prf.nextBytes(keys[4], 0, keys[4].length);
+ prf.nextBytes(keys[5], 0, keys[5].length);
+ }
+ }
+ catch (LimitReachedException lre)
+ {
+ // Won't happen with our implementation.
+ throw new Error(lre);
+ }
+ catch (NoSuchAlgorithmException nsae)
+ {
+ throw new Error(nsae);
+ }
+
+ if (Debug.DEBUG_KEY_EXCHANGE)
+ logger.logv(Component.SSL_KEY_EXCHANGE,
+ "keys generated;\n [0]: {0}\n [1]: {1}\n [2]: {2}\n" +
+ " [3]: {3}\n [4]: {4}\n [5]: {5}",
+ Util.toHexString(keys[0], ':'),
+ Util.toHexString(keys[1], ':'),
+ Util.toHexString(keys[2], ':'),
+ Util.toHexString(keys[3], ':'),
+ Util.toHexString(keys[4], ':'),
+ Util.toHexString(keys[5], ':'));
+ return keys;
+ }
+
+ /**
+ * Generate a "finished" message. The hashes passed in are modified
+ * by this function, so they should be clone copies of the digest if
+ * the hash function needs to be used more.
+ *
+ * @param md5 The MD5 computation.
+ * @param sha The SHA-1 computation.
+ * @param isClient Whether or not the client-side finished message is
+ * being computed.
+ * @param session The current session.
+ * @return A byte buffer containing the computed finished message.
+ */
+ protected ByteBuffer generateFinished(MessageDigest md5,
+ MessageDigest sha,
+ boolean isClient,
+ SessionImpl session)
+ {
+ ByteBuffer finishedBuffer = null;
+ if (session.version.compareTo(ProtocolVersion.TLS_1) >= 0)
+ {
+ finishedBuffer = ByteBuffer.allocate(12);
+ TLSRandom prf = new TLSRandom();
+ byte[] md5val = md5.digest();
+ byte[] shaval = sha.digest();
+ if (Debug.DEBUG)
+ logger.logv(Component.SSL_HANDSHAKE, "finished md5:{0} sha:{1}",
+ Util.toHexString(md5val, ':'),
+ Util.toHexString(shaval, ':'));
+ byte[] seed = new byte[CLIENT_FINISHED.length
+ + md5val.length
+ + shaval.length];
+ if (isClient)
+ System.arraycopy(CLIENT_FINISHED, 0, seed, 0, CLIENT_FINISHED.length);
+ else
+ System.arraycopy(SERVER_FINISHED, 0, seed, 0, SERVER_FINISHED.length);
+ System.arraycopy(md5val, 0,
+ seed, CLIENT_FINISHED.length,
+ md5val.length);
+ System.arraycopy(shaval, 0,
+ seed, CLIENT_FINISHED.length + md5val.length,
+ shaval.length);
+ HashMap<String, Object> params = new HashMap<String, Object>(2);
+ params.put(TLSRandom.SECRET, session.privateData.masterSecret);
+ params.put(TLSRandom.SEED, seed);
+ prf.init(params);
+ byte[] buf = new byte[12];
+ prf.nextBytes(buf, 0, buf.length);
+ finishedBuffer.put(buf).position(0);
+ }
+ else
+ {
+ // The SSLv3 algorithm is:
+ //
+ // enum { client(0x434C4E54), server(0x53525652) } Sender;
+ //
+ // struct {
+ // opaque md5_hash[16];
+ // opaque sha_hash[20];
+ // } Finished;
+ //
+ // md5_hash MD5(master_secret + pad2 +
+ // MD5(handshake_messages + Sender +
+ // master_secret + pad1));
+ // sha_hash SHA(master_secret + pad2 +
+ // SHA(handshake_messages + Sender +
+ // master_secret + pad1));
+ //
+
+ finishedBuffer = ByteBuffer.allocate(36);
+
+ md5.update(isClient ? SENDER_CLIENT : SENDER_SERVER);
+ md5.update(session.privateData.masterSecret);
+ md5.update(PAD1);
+
+ byte[] tmp = md5.digest();
+ md5.reset();
+ md5.update(session.privateData.masterSecret);
+ md5.update(PAD2);
+ md5.update(tmp);
+ finishedBuffer.put(md5.digest());
+
+ sha.update(isClient ? SENDER_CLIENT : SENDER_SERVER);
+ sha.update(session.privateData.masterSecret);
+ sha.update(PAD1, 0, 40);
+
+ tmp = sha.digest();
+ sha.reset();
+ sha.update(session.privateData.masterSecret);
+ sha.update(PAD2, 0, 40);
+ sha.update(tmp);
+ finishedBuffer.put(sha.digest()).position(0);
+ }
+ return finishedBuffer;
+ }
+
+ protected void initDiffieHellman(DHPrivateKey dhKey, SecureRandom random)
+ throws SSLException
+ {
+ try
+ {
+ keyAgreement = KeyAgreement.getInstance("DH");
+ keyAgreement.init(dhKey, random);
+ }
+ catch (InvalidKeyException ike)
+ {
+ throw new SSLException(ike);
+ }
+ catch (NoSuchAlgorithmException nsae)
+ {
+ throw new SSLException(nsae);
+ }
+ }
+
+ protected void generateMasterSecret(Random clientRandom,
+ Random serverRandom,
+ SessionImpl session)
+ throws SSLException
+ {
+ assert(clientRandom != null);
+ assert(serverRandom != null);
+ assert(session != null);
+
+ if (Debug.DEBUG_KEY_EXCHANGE)
+ logger.logv(Component.SSL_KEY_EXCHANGE, "preMasterSecret:\n{0}",
+ new ByteArray(preMasterSecret));
+
+ if (session.version == ProtocolVersion.SSL_3)
+ {
+ try
+ {
+ MessageDigest _md5 = MessageDigest.getInstance("MD5");
+ MessageDigest _sha = MessageDigest.getInstance("SHA");
+ session.privateData.masterSecret = new byte[48];
+
+ _sha.update((byte) 'A');
+ _sha.update(preMasterSecret);
+ _sha.update(clientRandom.buffer());
+ _sha.update(serverRandom.buffer());
+ _md5.update(preMasterSecret);
+ _md5.update(_sha.digest());
+ _md5.digest(session.privateData.masterSecret, 0, 16);
+
+ _sha.update((byte) 'B');
+ _sha.update((byte) 'B');
+ _sha.update(preMasterSecret);
+ _sha.update(clientRandom.buffer());
+ _sha.update(serverRandom.buffer());
+ _md5.update(preMasterSecret);
+ _md5.update(_sha.digest());
+ _md5.digest(session.privateData.masterSecret, 16, 16);
+
+ _sha.update((byte) 'C');
+ _sha.update((byte) 'C');
+ _sha.update((byte) 'C');
+ _sha.update(preMasterSecret);
+ _sha.update(clientRandom.buffer());
+ _sha.update(serverRandom.buffer());
+ _md5.update(preMasterSecret);
+ _md5.update(_sha.digest());
+ _md5.digest(session.privateData.masterSecret, 32, 16);
+ }
+ catch (DigestException de)
+ {
+ throw new SSLException(de);
+ }
+ catch (NoSuchAlgorithmException nsae)
+ {
+ throw new SSLException(nsae);
+ }
+ }
+ else // TLSv1.0 and later
+ {
+ byte[] seed = new byte[clientRandom.length()
+ + serverRandom.length()
+ + MASTER_SECRET.length];
+ System.arraycopy(MASTER_SECRET, 0, seed, 0, MASTER_SECRET.length);
+ clientRandom.buffer().get(seed, MASTER_SECRET.length,
+ clientRandom.length());
+ serverRandom.buffer().get(seed,
+ MASTER_SECRET.length + clientRandom.length(),
+ serverRandom.length());
+ TLSRandom prf = new TLSRandom();
+ HashMap<String,byte[]> attr = new HashMap<String,byte[]>(2);
+ attr.put(TLSRandom.SECRET, preMasterSecret);
+ attr.put(TLSRandom.SEED, seed);
+ prf.init(attr);
+
+ session.privateData.masterSecret = new byte[48];
+ prf.nextBytes(session.privateData.masterSecret, 0, 48);
+ }
+
+ if (Debug.DEBUG_KEY_EXCHANGE)
+ logger.log(Component.SSL_KEY_EXCHANGE, "master_secret: {0}",
+ new ByteArray(session.privateData.masterSecret));
+
+ // Wipe out the preMasterSecret.
+ for (int i = 0; i < preMasterSecret.length; i++)
+ preMasterSecret[i] = 0;
+ }
+
+ protected void setupSecurityParameters(byte[][] keys, boolean isClient,
+ SSLEngineImpl engine,
+ CompressionMethod compression)
+ throws SSLException
+ {
+ assert(keys.length == 6);
+ assert(engine != null);
+ assert(compression != null);
+
+ try
+ {
+ CipherSuite s = engine.session().suite;
+ Cipher inCipher = s.cipher();
+ Mac inMac = s.mac(engine.session().version);
+ Inflater inflater = (compression == CompressionMethod.ZLIB
+ ? new Inflater() : null);
+ inCipher.init(Cipher.DECRYPT_MODE,
+ new SecretKeySpec(keys[isClient ? 3 : 2],
+ s.cipherAlgorithm().toString()),
+ new IvParameterSpec(keys[isClient ? 5 : 4]));
+ inMac.init(new SecretKeySpec(keys[isClient ? 1 : 0],
+ inMac.getAlgorithm()));
+ inParams = new InputSecurityParameters(inCipher, inMac,
+ inflater,
+ engine.session(), s);
+
+ Cipher outCipher = s.cipher();
+ Mac outMac = s.mac(engine.session().version);
+ Deflater deflater = (compression == CompressionMethod.ZLIB
+ ? new Deflater() : null);
+ outCipher.init(Cipher.ENCRYPT_MODE,
+ new SecretKeySpec(keys[isClient ? 2 : 3],
+ s.cipherAlgorithm().toString()),
+ new IvParameterSpec(keys[isClient ? 4 : 5]));
+ outMac.init(new SecretKeySpec(keys[isClient ? 0 : 1],
+ outMac.getAlgorithm()));
+ outParams = new OutputSecurityParameters(outCipher, outMac,
+ deflater,
+ engine.session(), s);
+ }
+ catch (InvalidAlgorithmParameterException iape)
+ {
+ throw new SSLException(iape);
+ }
+ catch (InvalidKeyException ike)
+ {
+ throw new SSLException(ike);
+ }
+ catch (NoSuchAlgorithmException nsae)
+ {
+ throw new SSLException(nsae);
+ }
+ catch (NoSuchPaddingException nspe)
+ {
+ throw new SSLException(nspe);
+ }
+ }
+
+ protected void generatePSKSecret(String identity, byte[] otherkey,
+ boolean isClient)
+ throws SSLException
+ {
+ SecretKey key = null;
+ try
+ {
+ key = engine.contextImpl.pskManager.getKey(identity);
+ }
+ catch (KeyManagementException kme)
+ {
+ }
+ if (key != null)
+ {
+ byte[] keyb = key.getEncoded();
+ if (otherkey == null)
+ {
+ otherkey = new byte[keyb.length];
+ }
+ preMasterSecret = new byte[otherkey.length + keyb.length + 4];
+ preMasterSecret[0] = (byte) (otherkey.length >>> 8);
+ preMasterSecret[1] = (byte) otherkey.length;
+ System.arraycopy(otherkey, 0, preMasterSecret, 2, otherkey.length);
+ preMasterSecret[otherkey.length + 2]
+ = (byte) (keyb.length >>> 8);
+ preMasterSecret[otherkey.length + 3]
+ = (byte) keyb.length;
+ System.arraycopy(keyb, 0, preMasterSecret,
+ otherkey.length + 4, keyb.length);
+ }
+ else
+ {
+ // Generate a random, fake secret.
+ preMasterSecret = new byte[8];
+ preMasterSecret[1] = 2;
+ preMasterSecret[5] = 2;
+ preMasterSecret[6] = (byte) engine.session().random().nextInt();
+ preMasterSecret[7] = (byte) engine.session().random().nextInt();
+ }
+
+ if (Debug.DEBUG_KEY_EXCHANGE)
+ logger.logv(Component.SSL_KEY_EXCHANGE, "PSK identity {0} key {1}",
+ identity, key);
+
+ generateMasterSecret(clientRandom, serverRandom,
+ engine.session());
+ byte[][] keys = generateKeys(clientRandom, serverRandom,
+ engine.session());
+ setupSecurityParameters(keys, isClient, engine, compression);
+ }
+
+ protected class DHPhase extends DelegatedTask
+ {
+ private final DHPublicKey key;
+ private final boolean full;
+
+ protected DHPhase(DHPublicKey key)
+ {
+ this(key, true);
+ }
+
+ protected DHPhase(DHPublicKey key, boolean full)
+ {
+ this.key = key;
+ this.full = full;
+ }
+
+ protected void implRun() throws InvalidKeyException, SSLException
+ {
+ keyAgreement.doPhase(key, true);
+ preMasterSecret = keyAgreement.generateSecret();
+ if (full)
+ {
+ generateMasterSecret(clientRandom, serverRandom, engine.session());
+ byte[][] keys = generateKeys(clientRandom, serverRandom, engine.session());
+ setupSecurityParameters(keys, engine.getUseClientMode(), engine, compression);
+ }
+ }
+ }
+
+ protected class CertVerifier extends DelegatedTask
+ {
+ private final boolean clientSide;
+ private final X509Certificate[] chain;
+ private boolean verified;
+
+ protected CertVerifier(boolean clientSide, X509Certificate[] chain)
+ {
+ this.clientSide = clientSide;
+ this.chain = chain;
+ }
+
+ boolean verified()
+ {
+ return verified;
+ }
+
+ protected void implRun()
+ {
+ X509TrustManager tm = engine.contextImpl.trustManager;
+ if (clientSide)
+ {
+ try
+ {
+ tm.checkServerTrusted(chain, null);
+ verified = true;
+ }
+ catch (CertificateException ce)
+ {
+ if (Debug.DEBUG)
+ logger.log(Component.SSL_DELEGATED_TASK, "cert verify", ce);
+ // For client connections, ask the user if the certificate is OK.
+ CallbackHandler verify = new DefaultCallbackHandler();
+ GetSecurityPropertyAction gspa
+ = new GetSecurityPropertyAction("jessie.certificate.handler");
+ String clazz = AccessController.doPrivileged(gspa);
+ try
+ {
+ ClassLoader cl =
+ AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>()
+ {
+ public ClassLoader run() throws Exception
+ {
+ return ClassLoader.getSystemClassLoader();
+ }
+ });
+ verify = (CallbackHandler) cl.loadClass(clazz).newInstance();
+ }
+ catch (Exception x)
+ {
+ // Ignore.
+ if (Debug.DEBUG)
+ logger.log(Component.SSL_DELEGATED_TASK,
+ "callback handler loading", x);
+ }
+ // XXX Internationalize
+ CertificateCallback confirm =
+ new CertificateCallback(chain[0],
+ "The server's certificate could not be verified. There is no proof " +
+ "that this server is who it claims to be, or that their certificate " +
+ "is valid. Do you wish to continue connecting? ");
+
+ try
+ {
+ verify.handle(new Callback[] { confirm });
+ verified = confirm.getSelectedIndex() == ConfirmationCallback.YES;
+ }
+ catch (Exception x)
+ {
+ if (Debug.DEBUG)
+ logger.log(Component.SSL_DELEGATED_TASK,
+ "callback handler exception", x);
+ verified = false;
+ }
+ }
+ }
+ else
+ {
+ try
+ {
+ tm.checkClientTrusted(chain, null);
+ }
+ catch (CertificateException ce)
+ {
+ verified = false;
+ }
+ }
+
+ if (verified)
+ engine.session().setPeerVerified(true);
+ }
+ }
+
+ protected class DHE_PSKGen extends DelegatedTask
+ {
+ private final DHPublicKey dhKey;
+ private final SecretKey psKey;
+ private final boolean isClient;
+
+ protected DHE_PSKGen(DHPublicKey dhKey, SecretKey psKey, boolean isClient)
+ {
+ this.dhKey = dhKey;
+ this.psKey = psKey;
+ this.isClient = isClient;
+ }
+
+ /* (non-Javadoc)
+ * @see gnu.javax.net.ssl.provider.DelegatedTask#implRun()
+ */
+ @Override protected void implRun() throws Throwable
+ {
+ keyAgreement.doPhase(dhKey, true);
+ byte[] dhSecret = keyAgreement.generateSecret();
+ byte[] psSecret = null;
+ if (psKey != null)
+ psSecret = psKey.getEncoded();
+ else
+ {
+ psSecret = new byte[8];
+ engine.session().random().nextBytes(psSecret);
+ }
+
+ preMasterSecret = new byte[dhSecret.length + psSecret.length + 4];
+ preMasterSecret[0] = (byte) (dhSecret.length >>> 8);
+ preMasterSecret[1] = (byte) dhSecret.length;
+ System.arraycopy(dhSecret, 0, preMasterSecret, 2, dhSecret.length);
+ preMasterSecret[dhSecret.length + 2] = (byte) (psSecret.length >>> 8);
+ preMasterSecret[dhSecret.length + 3] = (byte) psSecret.length;
+ System.arraycopy(psSecret, 0, preMasterSecret, dhSecret.length + 4,
+ psSecret.length);
+
+ generateMasterSecret(clientRandom, serverRandom, engine.session());
+ byte[][] keys = generateKeys(clientRandom, serverRandom, engine.session());
+ setupSecurityParameters(keys, isClient, engine, compression);
+ }
+ }
+} \ No newline at end of file