/* * * * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program 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 version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.midp.ssl; import java.io.IOException; import java.io.InputStream; import java.lang.Exception; import java.util.Vector; import javax.microedition.pki.CertificateException; import com.sun.midp.log.Logging; import com.sun.midp.log.LogChannels; import com.sun.midp.crypto.*; import com.sun.midp.pki.*; /** * This class implements the SSL handshake protocol which is responsible * for negotiating security parameters used by the record layer. * Currently, only client-side functionality is implemented. */ // visible only within this package class Handshake { /** ARCFOUR_128_SHA (0x05). */ static final byte ARCFOUR_128_SHA = 0x05; /** ARCFOUR_128_MD5 (0x04). */ static final byte ARCFOUR_128_MD5 = 0x04; /** ARCFOUR_40_MD5 (0x03). */ static final byte ARCFOUR_40_MD5 = 0x03; /** * This contains the cipher suite encoding length in the first * two bytes, followed by an encoding of the cipher suites followed * by the compression suite length in one byte and the compression * suite. For now, we only propose the two most commonly used * cipher suites. */ private static final byte[] SUITES_AND_COMP = { // Use this to propose 128-bit encryption as preferred 0x00, 0x06, 0x00, ARCFOUR_128_SHA, 0x00, ARCFOUR_128_MD5, 0x00, ARCFOUR_40_MD5, 0x01, 0x00 // Use this to propose 40-bit encryption as preferred // 0x00, 0x06, 0x00, ARCFOUR_40_MD5, 0x00, ARCFOUR_128_RSA, // 0x00, ARCFOUR_128_SHA, 0x01, 0x00 }; /** * Array of suite names. */ private static String[] suiteNames = { "", "", "", "TLS_RSA_EXPORT_WITH_RC4_40_MD5", "TLS_RSA_WITH_RC4_128_MD5", "TLS_RSA_WITH_RC4_128_SHA" }; /** * Each handshake message has a four-byte header containing * the type (1 byte) and length (3 byte). */ private static final byte HDR_SIZE = 4; // Handshake message types /** Hello Request (0). */ private static final byte HELLO_REQ = 0; /** Client Hello (1). */ private static final byte C_HELLO = 1; /** Server Hello (2). */ private static final byte S_HELLO = 2; /** Certificate (11). */ private static final byte CERT = 11; /** Server Key Exchange (12). */ private static final byte S_KEYEXCH = 12; /** Certificate Request (13). */ private static final byte CERT_REQ = 13; /** Server Hello Done (14). */ private static final byte S_DONE = 14; /** Certificate Verify (15). */ private static final byte CERT_VRFY = 15; /** Client Key Exchange (16). */ private static final byte C_KEYEXCH = 16; /** Finished (20). */ private static final byte FINISH = 20; // Number of bytes in an MD5/SHA digest /** Number of bytes in an MD5 Digest (16). */ private static final byte MD5_SIZE = 16; /** Number of bytes in an SHA Digest (20). */ private static final byte SHA_SIZE = 20; /** * The Finish message contains one MD5 and one SHA hash * and has a length of 4+16+20 = 40 = 0x24 bytes. */ private static final byte[] FINISH_PREFIX = { FINISH, 0x00, 0x00, 0x24 }; /** Handle to trusted certificate store. */ private CertStore certStore = null; /** Current record to process. */ private Record rec; /** Peer host name . */ private String peerHost; /** Peer port number. */ private int peerPort; /** Local random number seed. */ private SecureRandom rnd = null; /** Previous session context to this host and port, if there was one. */ private Session cSession = null; /** Session id returned by server. */ private byte[] sSessionId = null; /** Client random number. */ private byte[] crand = null; /** Server random number. */ private byte[] srand = null; /** Proposed SSL version. */ private byte ver; /** Role (always CLIENT for now). */ private byte role; /** Negotiated cipher suite. */ byte negSuite; /** Name of negotiated cipher suite. */ String negSuiteName; /** Flag to indicate certificate request received. */ private byte gotCertReq = 0; /** Pre-master secret. */ private byte[] preMaster = null; /** Master secret. */ private byte[] master = null; /** * Public key used to encrypt the appropriate * usage of sKey certs in chain. */ private RSAPublicKey eKey = null; // we also need a temporary place to store the server certificate // in parseChain so it can be examined later rcvSrvrKeyExch() for // keyUsage checks and the parent connection. /** Temporary storage for server certificate. */ X509Certificate sCert = null; /* * These accumulate MD5 and SHA digests of all handshake * messages seen so far. */ /** Accumulation of MD5 digests. */ private MessageDigest ourMD5 = null; /** Accumulation of SHA digests. */ private MessageDigest ourSHA = null; /* * The following fields maintain a buffer of available handshake * messages. Note that a single SSL record may include multiple * handshake messages. */ /** Start of message in data buffer. */ private int start = 0; /** Start of next message in data buffer. */ private int nextMsgStart = 0; /** Count of bytes left in the data buffer. */ private int cnt = 0; /** * Validates a chain of certificates and returns the RSA public * key from the first certificate in that chain. The format of * the chain is specific to the ServerCertificate payload in an * SSL handshake. * * @param msg byte array containing the SSL ServerCertificate * payload (this is a chain of DER-encoded X.509 * certificates, in which each certificate is preceded * by a 3-byte length field) * @param off offset in the byte array where the cert chain begins * @param end position in the byte array where the cert chain ends + 1 * * @return server's certificate in the chain * * @exception IOException if the there is a binary formating error * @exception CertificateException if there a verification error */ private X509Certificate parseChain(byte[] msg, int off, int end) throws IOException, CertificateException { Vector certs = new Vector(); int len; // We have a 3-byte length field before each cert in list while (off < (end - 3)) { len = ((msg[off++] & 0xff) << 16) + ((msg[off++] & 0xff) << 8) + (msg[off++] & 0xff); if (len < 0 || len + off > msg.length) { throw new IOException("SSL certificate length too long"); } certs.addElement( X509Certificate.generateCertificate(msg, off, len)); off += len; } /* * The key usage extension of the server certificate is checked later * a based on the key exchange. Only the extended key usage is checked * now. */ X509Certificate.verifyChain(certs, -1, X509Certificate.SERVER_AUTH_EXT_KEY_USAGE, certStore); // The first cert if specified to be the server cert. return (X509Certificate)certs.elementAt(0); } /** * Creates an Handshake object that is used to negotiate a * version 3 handshake with an SSL peer. * * @param host hostname of the peer * @param port port number of the peer * @param r Record instance through which handshake * will occur * @param tcs trusted certificate store containing certificates * * @exception RuntimeException if SHA-1 or MD5 is not available */ Handshake(String host, int port, Record r, CertStore tcs) { peerHost = new String(host); peerPort = port; rec = r; certStore = tcs; eKey = null; gotCertReq = 0; start = 0; cnt = 0; try { ourMD5 = MessageDigest.getInstance("MD5"); ourSHA = MessageDigest.getInstance("SHA-1"); rnd = SecureRandom.getInstance(SecureRandom.ALG_SECURE_RANDOM); } catch (NoSuchAlgorithmException e) { // should only happen, if digests are not included in the build throw new RuntimeException(e.getMessage()); } } /** * Obtains the next available handshake message. * <p> * The message returned has the header plus the number of * bytes indicated in the handshake message header.</p> * * @param type the desired handshake message type * @return number of bytes in the next handshake message * of the desired type or -1 if the next message is not of * the desired type * @exception IOException if there is a problem reading the * next handshake message */ private int getNextMsg(byte type) throws IOException { if (cnt == 0) { rec.rdRec(true, Record.HNDSHK); if (rec.plainTextLength < HDR_SIZE) { throw new IOException("getNextMsg refill failed"); } cnt = rec.plainTextLength; nextMsgStart = 0; } if (rec.inputData[nextMsgStart] == type) { int len = ((rec.inputData[nextMsgStart + 1] & 0xff) << 16) + ((rec.inputData[nextMsgStart + 2] & 0xff) << 8) + (rec.inputData[nextMsgStart + 3] & 0xff) + HDR_SIZE; if (cnt < len) { throw new IOException("Refill got short msg " + "c=" + cnt + " l=" + len); } start = nextMsgStart; nextMsgStart += len; cnt -= len; return len; } else { return -1; } } /** * Sends an SSL version 3.0 Client hello handshake message. * <P /> * @exception IOException if there is a problem writing to * the record layer */ private void sndHello3() throws IOException { cSession = Session.get(peerHost, peerPort); int len = (cSession == null) ? 0 : cSession.id.length; /* * Size = 4 (HDR_SIZE) + 2 (client_version) + 32 (crand.length) + * 1 (session length) + len + 2 (cipher suite length) + * (2*CipherSuiteList.length) + 1 (compression length) + 1 (comp code) */ byte[] msg = new byte[39 + len + SUITES_AND_COMP.length]; int idx = 0; // Fill the header -- type (1 byte) length (3 bytes) msg[idx++] = C_HELLO; int mlen = msg.length - HDR_SIZE; msg[idx++] = (byte) (mlen >>> 16); msg[idx++] = (byte) (mlen >>> 8); msg[idx++] = (byte) (mlen & 0xff); // ... client_version msg[idx++] = (byte) (ver >>> 4); msg[idx++] = (byte) (ver & 0x0f); // ... random /* * IMPL_NOTE: overwrite the first four bytes of crand with * current time and date in standard 32-bit UNIX format. */ crand = new byte[32]; rnd.nextBytes(crand, 0, 32); System.arraycopy(crand, 0, msg, idx, crand.length); idx += crand.length; // ... session_id msg[idx++] = (byte) (len & 0xff); if (cSession != null) { System.arraycopy(cSession.id, 0, msg, idx, cSession.id.length); idx += cSession.id.length; } // ... cipher_suites and compression methods System.arraycopy(SUITES_AND_COMP, 0, msg, idx, SUITES_AND_COMP.length); ourMD5.update(msg, 0, msg.length); ourSHA.update(msg, 0, msg.length); // Finally, write this handshake record rec.wrRec(Record.HNDSHK, msg, 0, msg.length); } /** * Receives a Server hello handshake message. * <P /> * @return 0 on success, -1 on failure * @exception IOException if there is a problem reading the * message */ private int rcvSrvrHello() throws IOException { int msgLength = getNextMsg(S_HELLO); int idx = start + HDR_SIZE; int endOfMsg = start + msgLength; /* * Message must be long enough to contain a 4-byte header, * 2-byte version, a 32-byte random, a 1-byte session Id * length (plus variable lenght session Id), 2 byte cipher * suite, 1 byte compression method. */ if (msgLength < 42) { return -1; } // Get the server version if ((rec.inputData[start + idx++] != (ver >>> 4)) || (rec.inputData[start + idx++] != (ver & 0x0f))) { return -1; } // .. the 32-byte server random srand = new byte[32]; System.arraycopy(rec.inputData, idx, srand, 0, 32); idx += 32; // ... the session_Id length in 1 byte (and session_Id) int slen = rec.inputData[idx++] & 0xff; if (slen != 0) { if (endOfMsg < idx + slen) { return -1; } sSessionId = new byte[slen]; System.arraycopy(rec.inputData, idx, sSessionId, 0, slen); idx += slen; } // ... the cipher suite /* * NOTE: this impl works because the cipher suites * we support, the second byte directly maps to suite code. */ idx++; negSuite = rec.inputData[idx++]; /* * Check the cipher suite and compression method. The compression * method better be 0x00 since that is the only one we ever propose. */ if ((negSuite != ARCFOUR_128_SHA) && (negSuite != ARCFOUR_128_MD5) && (negSuite != ARCFOUR_40_MD5) && (rec.inputData[idx++] != (byte) 0x00)) { return -1; } // Update the hash of handshake messages ourMD5.update(rec.inputData, start, msgLength); ourSHA.update(rec.inputData, start, msgLength); negSuiteName = suiteNames[negSuite]; if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_SECURITY, "Negotiated " + negSuiteName); } return 0; } /** * Receives a Server certificate message containing a certificate * chain starting with the server certificate. * <P /> * @return 0 if a trustworthy server certificate is found, -1 otherwise * @exception IOException if there is a problem reading the message */ private int rcvCert() throws IOException { int msgLength; int endOfMsg; int idx; int len; msgLength = getNextMsg(CERT); endOfMsg = start + msgLength; /* * Message should atleast have a 4-byte header and an empty cert * list with 3-byte length */ if (msgLength < 7) { return -1; } idx = start + HDR_SIZE; len = 0; // Check the length ... len = ((rec.inputData[idx++] & 0xff) << 16) + ((rec.inputData[idx++] & 0xff) << 8) + (rec.inputData[idx++] & 0xff); if ((idx + len) > endOfMsg) return -1; // Parse the certificate chain and get the server's public key sCert = parseChain(rec.inputData, idx, endOfMsg); // Update the hash of handshake messages ourMD5.update(rec.inputData, start, msgLength); ourSHA.update(rec.inputData, start, msgLength); return 0; } /** * Receives a Server key exchange message. For now only RSA key * exchange is supported and this message includes temporary * RSA public key parameters signed by the server's long-term * private key. This message is optional. * <P /> * @return 0 on success, -1 on failure * @exception IOException if there is a problem reading the * message * @exception RuntimeException if SHA-1 or MD5 is not available */ private int rcvSrvrKeyExch() throws IOException { int msgLength = getNextMsg(S_KEYEXCH); int idx = start + HDR_SIZE; int endOfMsg = start + msgLength; RSAPublicKey sKey = (RSAPublicKey)sCert.getPublicKey(); int keyUsage = sCert.getKeyUsage(); /* * NOTE: Based on what we propose, the only key exch is RSA * Also note that the server key exchange is optional and used * only if the public key included in the certificate chain * is unsuitable for encrypting the pre-master secret. */ if (msgLength == -1) { // We can use the server key to encrypt premaster secret eKey = sKey; /* * Make sure sKey can be used for premaster secret encryption, * i.e. if key usage extension is present, the key encipherment * bit must be set */ if (keyUsage != -1 && (keyUsage & X509Certificate.KEY_ENCIPHER_KEY_USAGE) != X509Certificate.KEY_ENCIPHER_KEY_USAGE) { if (Logging.REPORT_LEVEL <= Logging.ERROR) { Logging.report(Logging.ERROR, LogChannels.LC_SECURITY, "The keyEncipherment was bit is " + "set in server certificate key " + "usage extension."); } throw new CertificateException(sCert, CertificateException.INAPPROPRIATE_KEY_USAGE); } return 0; } // read and verify the encryption key parameters if (endOfMsg < (idx + 4)) { return -1; } // read the modulus length int len = ((rec.inputData[idx++] & 0xff) << 16) + (rec.inputData[idx++] & 0xff); if (endOfMsg < (idx + len + 2)) { return -1; } int modulusPos; int modulusLen; int exponentPos; int exponentLen; // ... and the modulus /* * Some weird sites (e.g. www.verisign.com) encode a * 512-bit modulus in 65 (rather than 64 bytes) with the * first byte set to zero. We accomodate this behavior * by using a special check. */ if ((len == 65) && (rec.inputData[idx] == (byte)0x00)) { modulusPos = idx + 1; modulusLen = 64; } else { modulusPos = idx; modulusLen = len; } idx += len; // read the exponent length len = ((rec.inputData[idx++] & 0xff) << 16) + (rec.inputData[idx++] & 0xff); if (endOfMsg < (idx + len)) { return -1; } // ... and the exponent exponentPos = idx; exponentLen = len; eKey = new RSAPublicKey(rec.inputData, modulusPos, modulusLen, rec.inputData, exponentPos, exponentLen); idx += len; // mark where ServerRSAparams end int end = idx; // Now read the signature length len = ((rec.inputData[idx++] & 0xff) << 16) + (rec.inputData[idx++] & 0xff); if (endOfMsg < (idx + len)) { return -1; } // ... and the signature byte[] sig = new byte[len]; System.arraycopy(rec.inputData, idx, sig, 0, sig.length); idx += len; if (endOfMsg != idx) { return -1; } // Compute the expected hash byte[] dat = new byte[MD5_SIZE + SHA_SIZE]; try { MessageDigest di = MessageDigest.getInstance("MD5"); di.update(crand, 0, crand.length); di.update(srand, 0, srand.length); di.update(rec.inputData, HDR_SIZE, end - HDR_SIZE); di.digest(dat, 0, MD5_SIZE); di = MessageDigest.getInstance("SHA-1"); di.update(crand, 0, crand.length); di.update(srand, 0, srand.length); di.update(rec.inputData, HDR_SIZE, end - HDR_SIZE); di.digest(dat, MD5_SIZE, SHA_SIZE); } catch (Exception e) { throw new RuntimeException("No MD5 or SHA"); } try { Cipher rsa = Cipher.getInstance("RSA"); rsa.init(Cipher.DECRYPT_MODE, sKey); byte[] res = new byte[sKey.getModulusLen()]; int val = rsa.doFinal(sig, 0, sig.length, res, 0); if (!Utils.byteMatch(res, 0, dat, 0, dat.length)) { if (Logging.REPORT_LEVEL <= Logging.ERROR) { Logging.report(Logging.ERROR, LogChannels.LC_SECURITY, "RSA params failed verification"); } return -1; } } catch (Exception e) { throw new IOException("RSA decryption caught " + e); } // Update the hash of handshake messages ourMD5.update(rec.inputData, start, msgLength); ourSHA.update(rec.inputData, start, msgLength); return 0; } /** * Receives a Certificate request message. This message is optional. * <P /> * @return 0 (this method always completes successfully) * @exception IOException if there is a problem reading the * message */ private int rcvCertReq() throws IOException { int msgLength = getNextMsg(CERT_REQ); if (msgLength == -1) { return 0; // certificate request is optional } /* * We do not support client-side certificates so if we see * a request for a certificate, remember it here so we can * complain later */ gotCertReq = (byte) 1; // Update the hash of handshake messages ourMD5.update(rec.inputData, start, msgLength); ourSHA.update(rec.inputData, start, msgLength); // NOTE: We return zero without attempting to parse the message body. return 0; } /** * Receives a Server hello done message. * <P /> * @return 0 on success, -1 on error * @exception IOException if there is a problem reading the * message */ private int rcvSrvrHelloDone() throws IOException { int msgLength = getNextMsg(S_DONE); // A server_hello_done message has no body, just the header if (msgLength != HDR_SIZE) { return -1; } // Update the hash of handshake messages ourMD5.update(rec.inputData, start, msgLength); ourSHA.update(rec.inputData, start, msgLength); return 0; } /** * Sends a Client key exchange message. For now, only RSA key * exchange is supported and this message contains a pre-master * secret encrypted with the RSA public key of the server. * <P /> * @exception IOException if there is a problem writing to the * record layer */ private void sndKeyExch() throws IOException { /* * If we get here, the server agreed to an RSA key exchange * and the RSA public key to be used for encrypting the * pre-master secret is available in eKey. */ if (gotCertReq == 1) { // Send back an error ... we do not support client auth rec.alert(Record.FATAL, Record.NO_CERT); throw new IOException("No client cert"); } else { // NOTE: The only possible key exch is RSA // Generate a 48-byte random pre-master secret preMaster = new byte[48]; rnd.nextBytes(preMaster, 0, 48); // ... first two bytes must have client version preMaster[0] = (byte) (ver >>> 4); preMaster[1] = (byte) (ver & 0x0f); // Prepare a message containing the RSA encrypted pre-master int modLen = eKey.getModulusLen(); byte[] msg = new byte[HDR_SIZE + modLen]; int idx = 0; // Fill the type msg[idx++] = C_KEYEXCH; // ... message length msg[idx++] = (byte) (modLen >>> 16); msg[idx++] = (byte) (modLen >>> 8); msg[idx++] = (byte) (modLen & 0xff); // ... the encrypted pre-master secret try { Cipher rsa = Cipher.getInstance("RSA"); rsa.init(Cipher.ENCRYPT_MODE, eKey); int val = rsa.doFinal(preMaster, 0, 48, msg, idx); if (val != modLen) throw new IOException("RSA result too short"); } catch (Exception e) { throw new IOException("premaster encryption caught " + e); } // Update the hash of handshake messages ourMD5.update(msg, 0, msg.length); ourSHA.update(msg, 0, msg.length); rec.wrRec(Record.HNDSHK, msg, 0, msg.length); } } /** * Derives the master key based on the pre-master secret and * random values exchanged in the client and server hello messages. * <P /> * @exception IOException if there is a problem during the computation */ private void mkMaster() throws IOException { byte[] expansion[] = { { (byte) 0x41 }, // 'A' { (byte) 0x42, (byte) 0x42 }, // 'BB' { (byte) 0x43, (byte) 0x43, (byte) 0x43 }, // 'CCC' }; MessageDigest md = null; MessageDigest sd = null; /* * First, we compute the 48-byte (three MD5 outputs) master secret * * master_secret = * MD5(pre_master + SHA('A' + pre_master + * ClientHello.random + ServerHello.random)) + * MD5(pre_master + SHA('BB' + pre_master + * ClientHello.random + ServerHello.random)) + * MD5(pre_master + SHA('CCC' + pre_master + * ClientHello.random + ServerHello.random)); * * To simplify things, we use * tmp = pre_master + ClientHello.random + ServerHello.random; */ byte[] tmp = new byte[preMaster.length + crand.length + srand.length]; System.arraycopy(preMaster, 0, tmp, 0, preMaster.length); System.arraycopy(crand, 0, tmp, preMaster.length, crand.length); System.arraycopy(srand, 0, tmp, preMaster.length + crand.length, srand.length); try { md = MessageDigest.getInstance("MD5"); sd = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { /* * We should never catch this here (if these are missing, * we will catch this exception in the constructor) */ throw new RuntimeException("No MD5 or SHA"); } master = new byte[48]; try { for (int i = 0; i < 3; i++) { md.update(preMaster, 0, preMaster.length); sd.update(expansion[i], 0, expansion[i].length); byte[] res = new byte[SHA_SIZE]; sd.update(tmp, 0, tmp.length); sd.digest(res, 0, res.length); md.update(res, 0, res.length); md.digest(master, i << 4, MD5_SIZE); } } catch (DigestException e) { /* * We should never catch this here. */ throw new RuntimeException("digest exception"); } } /** * Sends a ChangeCipherSpec protocol message (this is not really * a handshake protocol message). * <P /> * @exception IOException if there is a problem writing to the * record layer */ private void sndChangeCipher() throws IOException { byte[] msg = new byte[1]; // change cipher spec consists of a single byte with value 1 msg[0] = (byte) 0x01; rec.wrRec(Record.CCS, msg, 0, 1); // msg.length is 1 } /** * Computes the content of a Finished message. * <P /> * @param who the role (either Record.CLIENT or * Record.SERVER) for which the finish message is computed * @return a byte array containing the hash of all handshake * messages seen so far * @exception IOException if handshake digests could not be computed */ private byte[] computeFinished(byte who) throws IOException { byte[] sender[] = { { 0x53, 0x52, 0x56, 0x52}, // for server { 0x43, 0x4c, 0x4e, 0x54} // for client }; byte[] msg = new byte[MD5_SIZE + SHA_SIZE]; byte[] tmp = null; try { // long t1 = System.currentTimeMillis(); MessageDigest d = (MessageDigest) ourMD5.clone(); d.update(sender[who], 0, 4); d.update(master, 0, master.length); tmp = new byte[MD5_SIZE]; // MD5 padding length is 48 d.update(MAC.PAD1, 0, 48); d.digest(tmp, 0, tmp.length); d.update(master, 0, master.length); d.update(MAC.PAD2, 0, 48); d.update(tmp, 0, tmp.length); d.digest(msg, 0, MD5_SIZE); d = (MessageDigest) ourSHA.clone(); d.update(sender[who], 0, 4); d.update(master, 0, master.length); tmp = new byte[SHA_SIZE]; // SHA padding length is 40 d.update(MAC.PAD1, 0, 40); d.digest(tmp, 0, tmp.length); d.update(master, 0, master.length); d.update(MAC.PAD2, 0, 40); d.update(tmp, 0, tmp.length); d.digest(msg, MD5_SIZE, SHA_SIZE); return msg; } catch (Exception e) { throw new IOException("MessageDigest not cloneable"); } } /** * Sends a Finished message. * <P /> * @exception IOException if there is a problem writing to the * record layer */ private void sndFinished() throws IOException { // HDR_SIZE + MD5_SIZE + SHA_SIZE is 40 byte[] msg = new byte[40]; System.arraycopy(FINISH_PREFIX, 0, msg, 0, 4); // MD5_SIZE + SHA_SIZE is 36 System.arraycopy(computeFinished(role), 0, msg, 4, 36); // Update the hash of handshake messages ourMD5.update(msg, 0, msg.length); ourSHA.update(msg, 0, msg.length); rec.wrRec(Record.HNDSHK, msg, 0, msg.length); } /** * Receives a ChangeCipherSpec protocol message (this is * not a handshake message). * <P /> * @return 0 on success, -1 on error * @exception IOException if there is a problem reading the * message */ private int rcvChangeCipher() throws IOException { /* * We make sure that there are no unread handshake messages * in the internal store when we get here. */ if (cnt != 0) { if (Logging.REPORT_LEVEL <= Logging.ERROR) { Logging.report(Logging.ERROR, LogChannels.LC_SECURITY, "Unread handshake mesg in store"); } return -1; } /* * Note that CCS is not a handshake message (it is its own protocol) * The record layer header is 5 bytes and the CCS body is one * byte with value 0x01. */ rec.rdRec(true, Record.CCS); if ((rec.inputData == null) || (rec.inputData.length != 1) || (rec.inputData[0] != (byte) 0x01)) { return -1; } return 0; } /** * Receives a Finished message and verifies that it contains * the correct hash of handshake messages. * <P /> * @return 0 on success, -1 on error * @exception IOException if there is a problem reading the * message */ private int rcvFinished() throws IOException { int msgLength = getNextMsg(FINISH); if (msgLength != 40) { return -1; } // Compute the expected hash byte[] expected = computeFinished((byte) (1 - role)); if (!Utils.byteMatch(rec.inputData, start + HDR_SIZE, expected, 0, expected.length)) { return -1; } else { // Update the hash of handshake messages ourMD5.update(rec.inputData, start, msgLength); ourSHA.update(rec.inputData, start, msgLength); // now = System.currentTimeMillis(); return 0; } } /** * Initiates an SSL handshake with the peer specified previously * in the constructor. * <P /> * @param aswho role played in the handshake (for now only * Record.CLIENT is supported) * @exception IOException if the handshake fails for some reason */ // IMPL_NOTE: Allow handshake parameters such as ver, cipher suites // and compression methods to be passed as arguments. void doHandShake(byte aswho) throws IOException { long t1 = System.currentTimeMillis(); int code = 0; ver = (byte) 0x30; // IMPL_NOTE: This is hardcoded for now role = aswho; byte val = 0; sndHello3(); if (rcvSrvrHello() < 0) { complain("Bad ServerHello"); }; if ((sSessionId == null) || (cSession == null) || (sSessionId.length != cSession.id.length) || !Utils.byteMatch(sSessionId, 0, cSession.id, 0, sSessionId.length)) { // Session not resumed try { code = rcvCert(); } catch (CertificateException e) { complain(e); } if (code < 0) { complain("Corrupt server certificate message"); } // ... get server_key_exchange (optional) try { code = rcvSrvrKeyExch(); } catch (CertificateException e) { complain(e); } if (code < 0) { complain("Bad ServerKeyExchange"); } // ... get certificate_request (optional) rcvCertReq(); if (rcvSrvrHelloDone() < 0) { complain("Bad ServerHelloDone"); } // ... send client_key_exchange sndKeyExch(); mkMaster(); try { rec.init(role, crand, srand, negSuite, master); } catch (Exception e) { complain("Record.init() caught " + e); } // ... send change_cipher_spec sndChangeCipher(); // ... send finished sndFinished(); // ... get change_cipher_spec if (rcvChangeCipher() < 0) { complain("Bad ChangeCipherSpec"); } // ... get finished if (rcvFinished() < 0) { complain("Bad Finished"); } } else { /* * The server agreed to resume a session. * Get the needed values from the previous session * now since the references could be overwritten if a * concurrent connection is made to this host and port. */ master = cSession.master; sCert = cSession.cert; try { rec.init(role, crand, srand, negSuite, master); } catch (Exception e) { complain("Record.init() caught " + e); } // ... get change_cipher_spec if (rcvChangeCipher() < 0) { complain("Bad ChangeCipherSpec"); } // ... get finished if (rcvFinished() < 0) { complain("Bad Finished"); } // ... send change_cipher_spec sndChangeCipher(); // ... send finished sndFinished(); } Session.add(peerHost, peerPort, sSessionId, master, sCert); // Zero out the premaster and master secrets if (preMaster != null) { // premaster can be null if we resumed an SSL session for (int i = 0; i < preMaster.length; i++) { preMaster[i] = 0; } } for (int i = 0; i < master.length; i++) { master[i] = 0; } } /** * Sends a fatal alert indicating handshake_failure and marks * the corresponding SSL session is non-resumable. * <p /> * @param msg string containing the exception message to be reported * @exception IOException with the specified string */ private void complain(String msg) throws IOException { complain(new IOException(msg)); } /** * Sends a fatal alert indicating handshake_failure and marks * the corresponding SSL session is non-resumable. * <p /> * @param e the IOException to be reported * @exception IOException */ private void complain(IOException e) throws IOException { try { rec.alert(Record.FATAL, Record.HNDSHK_FAIL); if (sSessionId != null) { Session.del(peerHost, peerPort, sSessionId); } } catch (Throwable t) { // Ignore, we are processing an exception currently } throw e; } } /** * This class implements methods to maintain resumable SSL * sessions. */ // visible within the package class Session { /** Maximum number of cached resumable sessions. */ private static final byte MAX_SESSIONS = 4; /** * Stores the last index where a session was overwritten, we * try to do a round-robin selection of places to overwrite */ private static int delIdx = 0; /* * A session is uniquely identified by the combination of * host, port and session identifier. The master secret is * included in the cached session information. */ /** Target host name. */ String host; /** Target port number. */ int port; /** Session identifier. */ byte[] id; /** Master secret. */ byte[] master; /** Target Certificate. */ X509Certificate cert; /** A cache of currently resumable sessions. */ private static Session[] sessions = new Session[MAX_SESSIONS]; /** * Gets the master secret associated with a resumable session. * The session is uniquely identified by the combination of the * host, port. * * @param h host name of peer * @param p port number of peer * * @return matching session */ static synchronized Session get(String h, int p) { for (int i = 0; i < MAX_SESSIONS; i++) { if ((sessions[i] == null) || (sessions[i].id == null)) continue; if (sessions[i].host.compareTo(h) == 0 && sessions[i].port == p) { return sessions[i]; } } return null; } /** * Adds a new session with the specified parameters to the cache * of resumable sessions. At any given time, this class maintains * at most one resusumable session for any host/port pair. * <P /> * @param h host name of peer * @param p port number of peer * @param id session identifier * @param mas master secret * @param cert certificate of peer */ static synchronized void add(String h, int p, byte[] id, byte[] mas, X509Certificate cert) { // IMPL_NOTE: This will change if we stop using linear arrays int idx = MAX_SESSIONS; for (int i = 0; i < MAX_SESSIONS; i++) { if ((sessions[i] == null) || (sessions[i].id == null)) { idx = i; // possible candidate for overwriting continue; } if ((sessions[i].host.compareTo(h) == 0) && (sessions[i].port == p)) { // preferred candidate idx = i; break; } } /* * If all else is taken, overwrite the one specified by * delIdx and move delIdx over to the next one. Simulates FIFO. */ if (idx == MAX_SESSIONS) { idx = delIdx; delIdx++; if (delIdx == MAX_SESSIONS) delIdx = 0; } if (sessions[idx] == null) { sessions[idx] = new Session(); } sessions[idx].id = id; /* * Since the master will change after this method, we need to * copy it, to preserve its current value for later. */ sessions[idx].master = new byte[mas.length]; System.arraycopy(mas, 0, sessions[idx].master, 0, mas.length); sessions[idx].host = new String(h); // "h" will be a substring of URL sessions[idx].port = p; sessions[idx].cert = cert; } /** * Deletes the session identified by the specfied parameters * from the cache of resumable sessions. * <P /> * @param h host name of peer * @param p port number of peer * @param sid session identifier */ static synchronized void del(String h, int p, byte[] sid) { for (int i = 0; i < MAX_SESSIONS; i++) { if ((sessions[i] == null) || (sessions[i].id == null)) continue; if (Utils.byteMatch(sessions[i].id, 0, sid, 0, sid.length) && (sessions[i].host.compareTo(h) == 0) && (sessions[i].port == p)) { sessions[i].id = null; sessions[i].master = null; sessions[i].host = null; sessions[i].cert = null; break; } } } }