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