0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026:
0027: package com.sun.midp.ssl;
0028:
0029: import java.io.IOException;
0030: import java.io.InputStream;
0031:
0032: import java.lang.Exception;
0033:
0034: import java.util.Vector;
0035:
0036: import javax.microedition.pki.CertificateException;
0037:
0038: import com.sun.midp.log.Logging;
0039: import com.sun.midp.log.LogChannels;
0040:
0041: import com.sun.midp.crypto.*;
0042:
0043: import com.sun.midp.pki.*;
0044:
0045: /**
0046: * This class implements the SSL handshake protocol which is responsible
0047: * for negotiating security parameters used by the record layer.
0048: * Currently, only client-side functionality is implemented.
0049: */
0050: // visible only within this package
0051: class Handshake {
0052: /** ARCFOUR_128_SHA (0x05). */
0053: static final byte ARCFOUR_128_SHA = 0x05;
0054: /** ARCFOUR_128_MD5 (0x04). */
0055: static final byte ARCFOUR_128_MD5 = 0x04;
0056: /** ARCFOUR_40_MD5 (0x03). */
0057: static final byte ARCFOUR_40_MD5 = 0x03;
0058:
0059: /**
0060: * This contains the cipher suite encoding length in the first
0061: * two bytes, followed by an encoding of the cipher suites followed
0062: * by the compression suite length in one byte and the compression
0063: * suite. For now, we only propose the two most commonly used
0064: * cipher suites.
0065: */
0066: private static final byte[] SUITES_AND_COMP = {
0067: // Use this to propose 128-bit encryption as preferred
0068: 0x00, 0x06, 0x00, ARCFOUR_128_SHA, 0x00, ARCFOUR_128_MD5,
0069: 0x00, ARCFOUR_40_MD5, 0x01, 0x00
0070: // Use this to propose 40-bit encryption as preferred
0071: // 0x00, 0x06, 0x00, ARCFOUR_40_MD5, 0x00, ARCFOUR_128_RSA,
0072: // 0x00, ARCFOUR_128_SHA, 0x01, 0x00
0073: };
0074:
0075: /**
0076: * Array of suite names.
0077: */
0078: private static String[] suiteNames = { "", "", "",
0079: "TLS_RSA_EXPORT_WITH_RC4_40_MD5",
0080: "TLS_RSA_WITH_RC4_128_MD5", "TLS_RSA_WITH_RC4_128_SHA" };
0081:
0082: /**
0083: * Each handshake message has a four-byte header containing
0084: * the type (1 byte) and length (3 byte).
0085: */
0086: private static final byte HDR_SIZE = 4;
0087:
0088: // Handshake message types
0089: /** Hello Request (0). */
0090: private static final byte HELLO_REQ = 0;
0091: /** Client Hello (1). */
0092: private static final byte C_HELLO = 1;
0093: /** Server Hello (2). */
0094: private static final byte S_HELLO = 2;
0095: /** Certificate (11). */
0096: private static final byte CERT = 11;
0097: /** Server Key Exchange (12). */
0098: private static final byte S_KEYEXCH = 12;
0099: /** Certificate Request (13). */
0100: private static final byte CERT_REQ = 13;
0101: /** Server Hello Done (14). */
0102: private static final byte S_DONE = 14;
0103: /** Certificate Verify (15). */
0104: private static final byte CERT_VRFY = 15;
0105: /** Client Key Exchange (16). */
0106: private static final byte C_KEYEXCH = 16;
0107: /** Finished (20). */
0108: private static final byte FINISH = 20;
0109:
0110: // Number of bytes in an MD5/SHA digest
0111: /** Number of bytes in an MD5 Digest (16). */
0112: private static final byte MD5_SIZE = 16;
0113: /** Number of bytes in an SHA Digest (20). */
0114: private static final byte SHA_SIZE = 20;
0115:
0116: /**
0117: * The Finish message contains one MD5 and one SHA hash
0118: * and has a length of 4+16+20 = 40 = 0x24 bytes.
0119: */
0120: private static final byte[] FINISH_PREFIX = { FINISH, 0x00, 0x00,
0121: 0x24 };
0122:
0123: /** Handle to trusted certificate store. */
0124: private CertStore certStore = null;
0125: /** Current record to process. */
0126: private Record rec;
0127: /** Peer host name . */
0128: private String peerHost;
0129: /** Peer port number. */
0130: private int peerPort;
0131: /** Local random number seed. */
0132: private SecureRandom rnd = null;
0133: /** Previous session context to this host and port, if there was one. */
0134: private Session cSession = null;
0135: /** Session id returned by server. */
0136: private byte[] sSessionId = null;
0137: /** Client random number. */
0138: private byte[] crand = null;
0139: /** Server random number. */
0140: private byte[] srand = null;
0141:
0142: /** Proposed SSL version. */
0143: private byte ver;
0144: /** Role (always CLIENT for now). */
0145: private byte role;
0146: /** Negotiated cipher suite. */
0147: byte negSuite;
0148: /** Name of negotiated cipher suite. */
0149: String negSuiteName;
0150: /** Flag to indicate certificate request received. */
0151: private byte gotCertReq = 0;
0152: /** Pre-master secret. */
0153: private byte[] preMaster = null;
0154: /** Master secret. */
0155: private byte[] master = null;
0156: /**
0157: * Public key used to encrypt the appropriate
0158: * usage of sKey certs in chain.
0159: */
0160: private RSAPublicKey eKey = null;
0161: // we also need a temporary place to store the server certificate
0162: // in parseChain so it can be examined later rcvSrvrKeyExch() for
0163: // keyUsage checks and the parent connection.
0164: /** Temporary storage for server certificate. */
0165: X509Certificate sCert = null;
0166:
0167: /*
0168: * These accumulate MD5 and SHA digests of all handshake
0169: * messages seen so far.
0170: */
0171: /** Accumulation of MD5 digests. */
0172: private MessageDigest ourMD5 = null;
0173: /** Accumulation of SHA digests. */
0174: private MessageDigest ourSHA = null;
0175:
0176: /*
0177: * The following fields maintain a buffer of available handshake
0178: * messages. Note that a single SSL record may include multiple
0179: * handshake messages.
0180: */
0181: /** Start of message in data buffer. */
0182: private int start = 0;
0183: /** Start of next message in data buffer. */
0184: private int nextMsgStart = 0;
0185: /** Count of bytes left in the data buffer. */
0186: private int cnt = 0;
0187:
0188: /**
0189: * Validates a chain of certificates and returns the RSA public
0190: * key from the first certificate in that chain. The format of
0191: * the chain is specific to the ServerCertificate payload in an
0192: * SSL handshake.
0193: *
0194: * @param msg byte array containing the SSL ServerCertificate
0195: * payload (this is a chain of DER-encoded X.509
0196: * certificates, in which each certificate is preceded
0197: * by a 3-byte length field)
0198: * @param off offset in the byte array where the cert chain begins
0199: * @param end position in the byte array where the cert chain ends + 1
0200: *
0201: * @return server's certificate in the chain
0202: *
0203: * @exception IOException if the there is a binary formating error
0204: * @exception CertificateException if there a verification error
0205: */
0206: private X509Certificate parseChain(byte[] msg, int off, int end)
0207: throws IOException, CertificateException {
0208:
0209: Vector certs = new Vector();
0210: int len;
0211:
0212: // We have a 3-byte length field before each cert in list
0213: while (off < (end - 3)) {
0214: len = ((msg[off++] & 0xff) << 16)
0215: + ((msg[off++] & 0xff) << 8) + (msg[off++] & 0xff);
0216:
0217: if (len < 0 || len + off > msg.length) {
0218: throw new IOException("SSL certificate length too long");
0219: }
0220:
0221: certs.addElement(X509Certificate.generateCertificate(msg,
0222: off, len));
0223:
0224: off += len;
0225: }
0226:
0227: /*
0228: * The key usage extension of the server certificate is checked later
0229: * a based on the key exchange. Only the extended key usage is checked
0230: * now.
0231: */
0232: X509Certificate.verifyChain(certs, -1,
0233: X509Certificate.SERVER_AUTH_EXT_KEY_USAGE, certStore);
0234:
0235: // The first cert if specified to be the server cert.
0236: return (X509Certificate) certs.elementAt(0);
0237: }
0238:
0239: /**
0240: * Creates an Handshake object that is used to negotiate a
0241: * version 3 handshake with an SSL peer.
0242: *
0243: * @param host hostname of the peer
0244: * @param port port number of the peer
0245: * @param r Record instance through which handshake
0246: * will occur
0247: * @param tcs trusted certificate store containing certificates
0248: *
0249: * @exception RuntimeException if SHA-1 or MD5 is not available
0250: */
0251: Handshake(String host, int port, Record r, CertStore tcs) {
0252: peerHost = new String(host);
0253: peerPort = port;
0254: rec = r;
0255: certStore = tcs;
0256: eKey = null;
0257: gotCertReq = 0;
0258: start = 0;
0259: cnt = 0;
0260:
0261: try {
0262: ourMD5 = MessageDigest.getInstance("MD5");
0263: ourSHA = MessageDigest.getInstance("SHA-1");
0264: rnd = SecureRandom
0265: .getInstance(SecureRandom.ALG_SECURE_RANDOM);
0266: } catch (NoSuchAlgorithmException e) {
0267: // should only happen, if digests are not included in the build
0268: throw new RuntimeException(e.getMessage());
0269: }
0270: }
0271:
0272: /**
0273: * Obtains the next available handshake message.
0274: * <p>
0275: * The message returned has the header plus the number of
0276: * bytes indicated in the handshake message header.</p>
0277: *
0278: * @param type the desired handshake message type
0279: * @return number of bytes in the next handshake message
0280: * of the desired type or -1 if the next message is not of
0281: * the desired type
0282: * @exception IOException if there is a problem reading the
0283: * next handshake message
0284: */
0285: private int getNextMsg(byte type) throws IOException {
0286: if (cnt == 0) {
0287: rec.rdRec(true, Record.HNDSHK);
0288:
0289: if (rec.plainTextLength < HDR_SIZE) {
0290: throw new IOException("getNextMsg refill failed");
0291: }
0292:
0293: cnt = rec.plainTextLength;
0294: nextMsgStart = 0;
0295: }
0296:
0297: if (rec.inputData[nextMsgStart] == type) {
0298: int len = ((rec.inputData[nextMsgStart + 1] & 0xff) << 16)
0299: + ((rec.inputData[nextMsgStart + 2] & 0xff) << 8)
0300: + (rec.inputData[nextMsgStart + 3] & 0xff)
0301: + HDR_SIZE;
0302:
0303: if (cnt < len) {
0304: throw new IOException("Refill got short msg " + "c="
0305: + cnt + " l=" + len);
0306: }
0307:
0308: start = nextMsgStart;
0309: nextMsgStart += len;
0310: cnt -= len;
0311: return len;
0312: } else {
0313: return -1;
0314: }
0315: }
0316:
0317: /**
0318: * Sends an SSL version 3.0 Client hello handshake message.
0319: * <P />
0320: * @exception IOException if there is a problem writing to
0321: * the record layer
0322: */
0323: private void sndHello3() throws IOException {
0324: cSession = Session.get(peerHost, peerPort);
0325: int len = (cSession == null) ? 0 : cSession.id.length;
0326: /*
0327: * Size = 4 (HDR_SIZE) + 2 (client_version) + 32 (crand.length) +
0328: * 1 (session length) + len + 2 (cipher suite length) +
0329: * (2*CipherSuiteList.length) + 1 (compression length) + 1 (comp code)
0330: */
0331: byte[] msg = new byte[39 + len + SUITES_AND_COMP.length];
0332: int idx = 0;
0333: // Fill the header -- type (1 byte) length (3 bytes)
0334: msg[idx++] = C_HELLO;
0335: int mlen = msg.length - HDR_SIZE;
0336: msg[idx++] = (byte) (mlen >>> 16);
0337: msg[idx++] = (byte) (mlen >>> 8);
0338: msg[idx++] = (byte) (mlen & 0xff);
0339: // ... client_version
0340: msg[idx++] = (byte) (ver >>> 4);
0341: msg[idx++] = (byte) (ver & 0x0f);
0342: // ... random
0343: /*
0344: * IMPL_NOTE: overwrite the first four bytes of crand with
0345: * current time and date in standard 32-bit UNIX format.
0346: */
0347: crand = new byte[32];
0348: rnd.nextBytes(crand, 0, 32);
0349: System.arraycopy(crand, 0, msg, idx, crand.length);
0350: idx += crand.length;
0351: // ... session_id
0352: msg[idx++] = (byte) (len & 0xff);
0353: if (cSession != null) {
0354: System.arraycopy(cSession.id, 0, msg, idx,
0355: cSession.id.length);
0356: idx += cSession.id.length;
0357: }
0358: // ... cipher_suites and compression methods
0359: System.arraycopy(SUITES_AND_COMP, 0, msg, idx,
0360: SUITES_AND_COMP.length);
0361:
0362: ourMD5.update(msg, 0, msg.length);
0363: ourSHA.update(msg, 0, msg.length);
0364:
0365: // Finally, write this handshake record
0366: rec.wrRec(Record.HNDSHK, msg, 0, msg.length);
0367: }
0368:
0369: /**
0370: * Receives a Server hello handshake message.
0371: * <P />
0372: * @return 0 on success, -1 on failure
0373: * @exception IOException if there is a problem reading the
0374: * message
0375: */
0376: private int rcvSrvrHello() throws IOException {
0377: int msgLength = getNextMsg(S_HELLO);
0378: int idx = start + HDR_SIZE;
0379: int endOfMsg = start + msgLength;
0380:
0381: /*
0382: * Message must be long enough to contain a 4-byte header,
0383: * 2-byte version, a 32-byte random, a 1-byte session Id
0384: * length (plus variable lenght session Id), 2 byte cipher
0385: * suite, 1 byte compression method.
0386: */
0387: if (msgLength < 42) {
0388: return -1;
0389: }
0390:
0391: // Get the server version
0392: if ((rec.inputData[start + idx++] != (ver >>> 4))
0393: || (rec.inputData[start + idx++] != (ver & 0x0f))) {
0394: return -1;
0395: }
0396:
0397: // .. the 32-byte server random
0398: srand = new byte[32];
0399: System.arraycopy(rec.inputData, idx, srand, 0, 32);
0400: idx += 32;
0401:
0402: // ... the session_Id length in 1 byte (and session_Id)
0403: int slen = rec.inputData[idx++] & 0xff;
0404: if (slen != 0) {
0405: if (endOfMsg < idx + slen) {
0406: return -1;
0407: }
0408:
0409: sSessionId = new byte[slen];
0410: System.arraycopy(rec.inputData, idx, sSessionId, 0, slen);
0411: idx += slen;
0412: }
0413:
0414: // ... the cipher suite
0415: /*
0416: * NOTE: this impl works because the cipher suites
0417: * we support, the second byte directly maps to suite code.
0418: */
0419: idx++;
0420: negSuite = rec.inputData[idx++];
0421:
0422: /*
0423: * Check the cipher suite and compression method. The compression
0424: * method better be 0x00 since that is the only one we ever propose.
0425: */
0426: if ((negSuite != ARCFOUR_128_SHA)
0427: && (negSuite != ARCFOUR_128_MD5)
0428: && (negSuite != ARCFOUR_40_MD5)
0429: && (rec.inputData[idx++] != (byte) 0x00)) {
0430: return -1;
0431: }
0432:
0433: // Update the hash of handshake messages
0434: ourMD5.update(rec.inputData, start, msgLength);
0435: ourSHA.update(rec.inputData, start, msgLength);
0436:
0437: negSuiteName = suiteNames[negSuite];
0438:
0439: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0440: Logging.report(Logging.INFORMATION,
0441: LogChannels.LC_SECURITY, "Negotiated "
0442: + negSuiteName);
0443: }
0444:
0445: return 0;
0446: }
0447:
0448: /**
0449: * Receives a Server certificate message containing a certificate
0450: * chain starting with the server certificate.
0451: * <P />
0452: * @return 0 if a trustworthy server certificate is found, -1 otherwise
0453: * @exception IOException if there is a problem reading the message
0454: */
0455: private int rcvCert() throws IOException {
0456: int msgLength;
0457: int endOfMsg;
0458: int idx;
0459: int len;
0460:
0461: msgLength = getNextMsg(CERT);
0462: endOfMsg = start + msgLength;
0463:
0464: /*
0465: * Message should atleast have a 4-byte header and an empty cert
0466: * list with 3-byte length
0467: */
0468: if (msgLength < 7) {
0469: return -1;
0470: }
0471:
0472: idx = start + HDR_SIZE;
0473: len = 0;
0474:
0475: // Check the length ...
0476: len = ((rec.inputData[idx++] & 0xff) << 16)
0477: + ((rec.inputData[idx++] & 0xff) << 8)
0478: + (rec.inputData[idx++] & 0xff);
0479: if ((idx + len) > endOfMsg)
0480: return -1;
0481:
0482: // Parse the certificate chain and get the server's public key
0483: sCert = parseChain(rec.inputData, idx, endOfMsg);
0484:
0485: // Update the hash of handshake messages
0486: ourMD5.update(rec.inputData, start, msgLength);
0487: ourSHA.update(rec.inputData, start, msgLength);
0488:
0489: return 0;
0490: }
0491:
0492: /**
0493: * Receives a Server key exchange message. For now only RSA key
0494: * exchange is supported and this message includes temporary
0495: * RSA public key parameters signed by the server's long-term
0496: * private key. This message is optional.
0497: * <P />
0498: * @return 0 on success, -1 on failure
0499: * @exception IOException if there is a problem reading the
0500: * message
0501: * @exception RuntimeException if SHA-1 or MD5 is not available
0502: */
0503: private int rcvSrvrKeyExch() throws IOException {
0504: int msgLength = getNextMsg(S_KEYEXCH);
0505: int idx = start + HDR_SIZE;
0506: int endOfMsg = start + msgLength;
0507: RSAPublicKey sKey = (RSAPublicKey) sCert.getPublicKey();
0508: int keyUsage = sCert.getKeyUsage();
0509:
0510: /*
0511: * NOTE: Based on what we propose, the only key exch is RSA
0512: * Also note that the server key exchange is optional and used
0513: * only if the public key included in the certificate chain
0514: * is unsuitable for encrypting the pre-master secret.
0515: */
0516: if (msgLength == -1) {
0517: // We can use the server key to encrypt premaster secret
0518: eKey = sKey;
0519:
0520: /*
0521: * Make sure sKey can be used for premaster secret encryption,
0522: * i.e. if key usage extension is present, the key encipherment
0523: * bit must be set
0524: */
0525: if (keyUsage != -1
0526: && (keyUsage & X509Certificate.KEY_ENCIPHER_KEY_USAGE) != X509Certificate.KEY_ENCIPHER_KEY_USAGE) {
0527: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
0528: Logging.report(Logging.ERROR,
0529: LogChannels.LC_SECURITY,
0530: "The keyEncipherment was bit is "
0531: + "set in server certificate key "
0532: + "usage extension.");
0533: }
0534: throw new CertificateException(sCert,
0535: CertificateException.INAPPROPRIATE_KEY_USAGE);
0536: }
0537:
0538: return 0;
0539: }
0540:
0541: // read and verify the encryption key parameters
0542: if (endOfMsg < (idx + 4)) {
0543: return -1;
0544: }
0545:
0546: // read the modulus length
0547: int len = ((rec.inputData[idx++] & 0xff) << 16)
0548: + (rec.inputData[idx++] & 0xff);
0549: if (endOfMsg < (idx + len + 2)) {
0550: return -1;
0551: }
0552:
0553: int modulusPos;
0554: int modulusLen;
0555: int exponentPos;
0556: int exponentLen;
0557:
0558: // ... and the modulus
0559: /*
0560: * Some weird sites (e.g. www.verisign.com) encode a
0561: * 512-bit modulus in 65 (rather than 64 bytes) with the
0562: * first byte set to zero. We accomodate this behavior
0563: * by using a special check.
0564: */
0565: if ((len == 65) && (rec.inputData[idx] == (byte) 0x00)) {
0566: modulusPos = idx + 1;
0567: modulusLen = 64;
0568: } else {
0569: modulusPos = idx;
0570: modulusLen = len;
0571: }
0572:
0573: idx += len;
0574:
0575: // read the exponent length
0576: len = ((rec.inputData[idx++] & 0xff) << 16)
0577: + (rec.inputData[idx++] & 0xff);
0578: if (endOfMsg < (idx + len)) {
0579: return -1;
0580: }
0581:
0582: // ... and the exponent
0583: exponentPos = idx;
0584: exponentLen = len;
0585:
0586: eKey = new RSAPublicKey(rec.inputData, modulusPos, modulusLen,
0587: rec.inputData, exponentPos, exponentLen);
0588:
0589: idx += len;
0590:
0591: // mark where ServerRSAparams end
0592: int end = idx;
0593:
0594: // Now read the signature length
0595: len = ((rec.inputData[idx++] & 0xff) << 16)
0596: + (rec.inputData[idx++] & 0xff);
0597: if (endOfMsg < (idx + len)) {
0598: return -1;
0599: }
0600:
0601: // ... and the signature
0602: byte[] sig = new byte[len];
0603: System.arraycopy(rec.inputData, idx, sig, 0, sig.length);
0604: idx += len;
0605: if (endOfMsg != idx) {
0606: return -1;
0607: }
0608:
0609: // Compute the expected hash
0610: byte[] dat = new byte[MD5_SIZE + SHA_SIZE];
0611: try {
0612: MessageDigest di = MessageDigest.getInstance("MD5");
0613:
0614: di.update(crand, 0, crand.length);
0615: di.update(srand, 0, srand.length);
0616: di.update(rec.inputData, HDR_SIZE, end - HDR_SIZE);
0617: di.digest(dat, 0, MD5_SIZE);
0618:
0619: di = MessageDigest.getInstance("SHA-1");
0620: di.update(crand, 0, crand.length);
0621: di.update(srand, 0, srand.length);
0622: di.update(rec.inputData, HDR_SIZE, end - HDR_SIZE);
0623: di.digest(dat, MD5_SIZE, SHA_SIZE);
0624: } catch (Exception e) {
0625: throw new RuntimeException("No MD5 or SHA");
0626: }
0627:
0628: try {
0629: Cipher rsa = Cipher.getInstance("RSA");
0630: rsa.init(Cipher.DECRYPT_MODE, sKey);
0631: byte[] res = new byte[sKey.getModulusLen()];
0632: int val = rsa.doFinal(sig, 0, sig.length, res, 0);
0633: if (!Utils.byteMatch(res, 0, dat, 0, dat.length)) {
0634: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
0635: Logging.report(Logging.ERROR,
0636: LogChannels.LC_SECURITY,
0637: "RSA params failed verification");
0638: }
0639: return -1;
0640: }
0641: } catch (Exception e) {
0642: throw new IOException("RSA decryption caught " + e);
0643: }
0644:
0645: // Update the hash of handshake messages
0646: ourMD5.update(rec.inputData, start, msgLength);
0647: ourSHA.update(rec.inputData, start, msgLength);
0648:
0649: return 0;
0650: }
0651:
0652: /**
0653: * Receives a Certificate request message. This message is optional.
0654: * <P />
0655: * @return 0 (this method always completes successfully)
0656: * @exception IOException if there is a problem reading the
0657: * message
0658: */
0659: private int rcvCertReq() throws IOException {
0660: int msgLength = getNextMsg(CERT_REQ);
0661: if (msgLength == -1) {
0662: return 0; // certificate request is optional
0663: }
0664:
0665: /*
0666: * We do not support client-side certificates so if we see
0667: * a request for a certificate, remember it here so we can
0668: * complain later
0669: */
0670: gotCertReq = (byte) 1;
0671:
0672: // Update the hash of handshake messages
0673: ourMD5.update(rec.inputData, start, msgLength);
0674: ourSHA.update(rec.inputData, start, msgLength);
0675:
0676: // NOTE: We return zero without attempting to parse the message body.
0677: return 0;
0678: }
0679:
0680: /**
0681: * Receives a Server hello done message.
0682: * <P />
0683: * @return 0 on success, -1 on error
0684: * @exception IOException if there is a problem reading the
0685: * message
0686: */
0687: private int rcvSrvrHelloDone() throws IOException {
0688: int msgLength = getNextMsg(S_DONE);
0689:
0690: // A server_hello_done message has no body, just the header
0691: if (msgLength != HDR_SIZE) {
0692: return -1;
0693: }
0694:
0695: // Update the hash of handshake messages
0696: ourMD5.update(rec.inputData, start, msgLength);
0697: ourSHA.update(rec.inputData, start, msgLength);
0698:
0699: return 0;
0700: }
0701:
0702: /**
0703: * Sends a Client key exchange message. For now, only RSA key
0704: * exchange is supported and this message contains a pre-master
0705: * secret encrypted with the RSA public key of the server.
0706: * <P />
0707: * @exception IOException if there is a problem writing to the
0708: * record layer
0709: */
0710: private void sndKeyExch() throws IOException {
0711: /*
0712: * If we get here, the server agreed to an RSA key exchange
0713: * and the RSA public key to be used for encrypting the
0714: * pre-master secret is available in eKey.
0715: */
0716: if (gotCertReq == 1) {
0717: // Send back an error ... we do not support client auth
0718: rec.alert(Record.FATAL, Record.NO_CERT);
0719: throw new IOException("No client cert");
0720: } else { // NOTE: The only possible key exch is RSA
0721:
0722: // Generate a 48-byte random pre-master secret
0723: preMaster = new byte[48];
0724:
0725: rnd.nextBytes(preMaster, 0, 48);
0726: // ... first two bytes must have client version
0727: preMaster[0] = (byte) (ver >>> 4);
0728: preMaster[1] = (byte) (ver & 0x0f);
0729:
0730: // Prepare a message containing the RSA encrypted pre-master
0731: int modLen = eKey.getModulusLen();
0732: byte[] msg = new byte[HDR_SIZE + modLen];
0733: int idx = 0;
0734: // Fill the type
0735: msg[idx++] = C_KEYEXCH;
0736: // ... message length
0737: msg[idx++] = (byte) (modLen >>> 16);
0738: msg[idx++] = (byte) (modLen >>> 8);
0739: msg[idx++] = (byte) (modLen & 0xff);
0740:
0741: // ... the encrypted pre-master secret
0742: try {
0743: Cipher rsa = Cipher.getInstance("RSA");
0744:
0745: rsa.init(Cipher.ENCRYPT_MODE, eKey);
0746: int val = rsa.doFinal(preMaster, 0, 48, msg, idx);
0747: if (val != modLen)
0748: throw new IOException("RSA result too short");
0749: } catch (Exception e) {
0750: throw new IOException("premaster encryption caught "
0751: + e);
0752: }
0753:
0754: // Update the hash of handshake messages
0755: ourMD5.update(msg, 0, msg.length);
0756: ourSHA.update(msg, 0, msg.length);
0757:
0758: rec.wrRec(Record.HNDSHK, msg, 0, msg.length);
0759: }
0760: }
0761:
0762: /**
0763: * Derives the master key based on the pre-master secret and
0764: * random values exchanged in the client and server hello messages.
0765: * <P />
0766: * @exception IOException if there is a problem during the computation
0767: */
0768: private void mkMaster() throws IOException {
0769: byte[] expansion[] = { { (byte) 0x41 }, // 'A'
0770: { (byte) 0x42, (byte) 0x42 }, // 'BB'
0771: { (byte) 0x43, (byte) 0x43, (byte) 0x43 }, // 'CCC'
0772: };
0773:
0774: MessageDigest md = null;
0775: MessageDigest sd = null;
0776:
0777: /*
0778: * First, we compute the 48-byte (three MD5 outputs) master secret
0779: *
0780: * master_secret =
0781: * MD5(pre_master + SHA('A' + pre_master +
0782: * ClientHello.random + ServerHello.random)) +
0783: * MD5(pre_master + SHA('BB' + pre_master +
0784: * ClientHello.random + ServerHello.random)) +
0785: * MD5(pre_master + SHA('CCC' + pre_master +
0786: * ClientHello.random + ServerHello.random));
0787: *
0788: * To simplify things, we use
0789: * tmp = pre_master + ClientHello.random + ServerHello.random;
0790: */
0791: byte[] tmp = new byte[preMaster.length + crand.length
0792: + srand.length];
0793: System.arraycopy(preMaster, 0, tmp, 0, preMaster.length);
0794: System.arraycopy(crand, 0, tmp, preMaster.length, crand.length);
0795: System.arraycopy(srand, 0, tmp,
0796: preMaster.length + crand.length, srand.length);
0797: try {
0798: md = MessageDigest.getInstance("MD5");
0799: sd = MessageDigest.getInstance("SHA-1");
0800: } catch (NoSuchAlgorithmException e) {
0801: /*
0802: * We should never catch this here (if these are missing,
0803: * we will catch this exception in the constructor)
0804: */
0805: throw new RuntimeException("No MD5 or SHA");
0806: }
0807: master = new byte[48];
0808:
0809: try {
0810: for (int i = 0; i < 3; i++) {
0811: md.update(preMaster, 0, preMaster.length);
0812: sd.update(expansion[i], 0, expansion[i].length);
0813: byte[] res = new byte[SHA_SIZE];
0814: sd.update(tmp, 0, tmp.length);
0815: sd.digest(res, 0, res.length);
0816: md.update(res, 0, res.length);
0817: md.digest(master, i << 4, MD5_SIZE);
0818: }
0819: } catch (DigestException e) {
0820: /*
0821: * We should never catch this here.
0822: */
0823: throw new RuntimeException("digest exception");
0824: }
0825: }
0826:
0827: /**
0828: * Sends a ChangeCipherSpec protocol message (this is not really
0829: * a handshake protocol message).
0830: * <P />
0831: * @exception IOException if there is a problem writing to the
0832: * record layer
0833: */
0834: private void sndChangeCipher() throws IOException {
0835: byte[] msg = new byte[1];
0836: // change cipher spec consists of a single byte with value 1
0837: msg[0] = (byte) 0x01;
0838: rec.wrRec(Record.CCS, msg, 0, 1); // msg.length is 1
0839: }
0840:
0841: /**
0842: * Computes the content of a Finished message.
0843: * <P />
0844: * @param who the role (either Record.CLIENT or
0845: * Record.SERVER) for which the finish message is computed
0846: * @return a byte array containing the hash of all handshake
0847: * messages seen so far
0848: * @exception IOException if handshake digests could not be computed
0849: */
0850: private byte[] computeFinished(byte who) throws IOException {
0851: byte[] sender[] = { { 0x53, 0x52, 0x56, 0x52 }, // for server
0852: { 0x43, 0x4c, 0x4e, 0x54 } // for client
0853: };
0854: byte[] msg = new byte[MD5_SIZE + SHA_SIZE];
0855: byte[] tmp = null;
0856:
0857: try {
0858: // long t1 = System.currentTimeMillis();
0859: MessageDigest d = (MessageDigest) ourMD5.clone();
0860: d.update(sender[who], 0, 4);
0861: d.update(master, 0, master.length);
0862: tmp = new byte[MD5_SIZE];
0863: // MD5 padding length is 48
0864: d.update(MAC.PAD1, 0, 48);
0865: d.digest(tmp, 0, tmp.length);
0866: d.update(master, 0, master.length);
0867: d.update(MAC.PAD2, 0, 48);
0868: d.update(tmp, 0, tmp.length);
0869: d.digest(msg, 0, MD5_SIZE);
0870:
0871: d = (MessageDigest) ourSHA.clone();
0872: d.update(sender[who], 0, 4);
0873: d.update(master, 0, master.length);
0874: tmp = new byte[SHA_SIZE];
0875: // SHA padding length is 40
0876: d.update(MAC.PAD1, 0, 40);
0877: d.digest(tmp, 0, tmp.length);
0878: d.update(master, 0, master.length);
0879: d.update(MAC.PAD2, 0, 40);
0880: d.update(tmp, 0, tmp.length);
0881: d.digest(msg, MD5_SIZE, SHA_SIZE);
0882:
0883: return msg;
0884: } catch (Exception e) {
0885: throw new IOException("MessageDigest not cloneable");
0886: }
0887: }
0888:
0889: /**
0890: * Sends a Finished message.
0891: * <P />
0892: * @exception IOException if there is a problem writing to the
0893: * record layer
0894: */
0895: private void sndFinished() throws IOException {
0896: // HDR_SIZE + MD5_SIZE + SHA_SIZE is 40
0897: byte[] msg = new byte[40];
0898:
0899: System.arraycopy(FINISH_PREFIX, 0, msg, 0, 4);
0900: // MD5_SIZE + SHA_SIZE is 36
0901: System.arraycopy(computeFinished(role), 0, msg, 4, 36);
0902:
0903: // Update the hash of handshake messages
0904: ourMD5.update(msg, 0, msg.length);
0905: ourSHA.update(msg, 0, msg.length);
0906:
0907: rec.wrRec(Record.HNDSHK, msg, 0, msg.length);
0908: }
0909:
0910: /**
0911: * Receives a ChangeCipherSpec protocol message (this is
0912: * not a handshake message).
0913: * <P />
0914: * @return 0 on success, -1 on error
0915: * @exception IOException if there is a problem reading the
0916: * message
0917: */
0918: private int rcvChangeCipher() throws IOException {
0919: /*
0920: * We make sure that there are no unread handshake messages
0921: * in the internal store when we get here.
0922: */
0923: if (cnt != 0) {
0924: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
0925: Logging.report(Logging.ERROR, LogChannels.LC_SECURITY,
0926: "Unread handshake mesg in store");
0927: }
0928: return -1;
0929: }
0930:
0931: /*
0932: * Note that CCS is not a handshake message (it is its own protocol)
0933: * The record layer header is 5 bytes and the CCS body is one
0934: * byte with value 0x01.
0935: */
0936: rec.rdRec(true, Record.CCS);
0937: if ((rec.inputData == null) || (rec.inputData.length != 1)
0938: || (rec.inputData[0] != (byte) 0x01)) {
0939: return -1;
0940: }
0941:
0942: return 0;
0943: }
0944:
0945: /**
0946: * Receives a Finished message and verifies that it contains
0947: * the correct hash of handshake messages.
0948: * <P />
0949: * @return 0 on success, -1 on error
0950: * @exception IOException if there is a problem reading the
0951: * message
0952: */
0953: private int rcvFinished() throws IOException {
0954: int msgLength = getNextMsg(FINISH);
0955: if (msgLength != 40) {
0956: return -1;
0957: }
0958:
0959: // Compute the expected hash
0960: byte[] expected = computeFinished((byte) (1 - role));
0961:
0962: if (!Utils.byteMatch(rec.inputData, start + HDR_SIZE, expected,
0963: 0, expected.length)) {
0964: return -1;
0965: } else {
0966: // Update the hash of handshake messages
0967: ourMD5.update(rec.inputData, start, msgLength);
0968: ourSHA.update(rec.inputData, start, msgLength);
0969: // now = System.currentTimeMillis();
0970: return 0;
0971: }
0972: }
0973:
0974: /**
0975: * Initiates an SSL handshake with the peer specified previously
0976: * in the constructor.
0977: * <P />
0978: * @param aswho role played in the handshake (for now only
0979: * Record.CLIENT is supported)
0980: * @exception IOException if the handshake fails for some reason
0981: */
0982: // IMPL_NOTE: Allow handshake parameters such as ver, cipher suites
0983: // and compression methods to be passed as arguments.
0984: void doHandShake(byte aswho) throws IOException {
0985: long t1 = System.currentTimeMillis();
0986: int code = 0;
0987:
0988: ver = (byte) 0x30; // IMPL_NOTE: This is hardcoded for now
0989: role = aswho;
0990:
0991: byte val = 0;
0992: sndHello3();
0993:
0994: if (rcvSrvrHello() < 0) {
0995: complain("Bad ServerHello");
0996: }
0997: ;
0998:
0999: if ((sSessionId == null)
1000: || (cSession == null)
1001: || (sSessionId.length != cSession.id.length)
1002: || !Utils.byteMatch(sSessionId, 0, cSession.id, 0,
1003: sSessionId.length)) {
1004: // Session not resumed
1005:
1006: try {
1007: code = rcvCert();
1008: } catch (CertificateException e) {
1009: complain(e);
1010: }
1011:
1012: if (code < 0) {
1013: complain("Corrupt server certificate message");
1014: }
1015:
1016: // ... get server_key_exchange (optional)
1017: try {
1018: code = rcvSrvrKeyExch();
1019: } catch (CertificateException e) {
1020: complain(e);
1021: }
1022:
1023: if (code < 0) {
1024: complain("Bad ServerKeyExchange");
1025: }
1026:
1027: // ... get certificate_request (optional)
1028: rcvCertReq();
1029: if (rcvSrvrHelloDone() < 0) {
1030: complain("Bad ServerHelloDone");
1031: }
1032:
1033: // ... send client_key_exchange
1034: sndKeyExch();
1035: mkMaster();
1036: try {
1037: rec.init(role, crand, srand, negSuite, master);
1038: } catch (Exception e) {
1039: complain("Record.init() caught " + e);
1040: }
1041:
1042: // ... send change_cipher_spec
1043: sndChangeCipher();
1044: // ... send finished
1045: sndFinished();
1046:
1047: // ... get change_cipher_spec
1048: if (rcvChangeCipher() < 0) {
1049: complain("Bad ChangeCipherSpec");
1050: }
1051:
1052: // ... get finished
1053: if (rcvFinished() < 0) {
1054: complain("Bad Finished");
1055: }
1056: } else {
1057: /*
1058: * The server agreed to resume a session.
1059: * Get the needed values from the previous session
1060: * now since the references could be overwritten if a
1061: * concurrent connection is made to this host and port.
1062: */
1063: master = cSession.master;
1064: sCert = cSession.cert;
1065:
1066: try {
1067: rec.init(role, crand, srand, negSuite, master);
1068: } catch (Exception e) {
1069: complain("Record.init() caught " + e);
1070: }
1071:
1072: // ... get change_cipher_spec
1073: if (rcvChangeCipher() < 0) {
1074: complain("Bad ChangeCipherSpec");
1075: }
1076:
1077: // ... get finished
1078: if (rcvFinished() < 0) {
1079: complain("Bad Finished");
1080: }
1081:
1082: // ... send change_cipher_spec
1083: sndChangeCipher();
1084: // ... send finished
1085: sndFinished();
1086: }
1087:
1088: Session.add(peerHost, peerPort, sSessionId, master, sCert);
1089:
1090: // Zero out the premaster and master secrets
1091: if (preMaster != null) {
1092: // premaster can be null if we resumed an SSL session
1093: for (int i = 0; i < preMaster.length; i++) {
1094: preMaster[i] = 0;
1095: }
1096: }
1097:
1098: for (int i = 0; i < master.length; i++) {
1099: master[i] = 0;
1100: }
1101: }
1102:
1103: /**
1104: * Sends a fatal alert indicating handshake_failure and marks
1105: * the corresponding SSL session is non-resumable.
1106: * <p />
1107: * @param msg string containing the exception message to be reported
1108: * @exception IOException with the specified string
1109: */
1110: private void complain(String msg) throws IOException {
1111: complain(new IOException(msg));
1112: }
1113:
1114: /**
1115: * Sends a fatal alert indicating handshake_failure and marks
1116: * the corresponding SSL session is non-resumable.
1117: * <p />
1118: * @param e the IOException to be reported
1119: * @exception IOException
1120: */
1121: private void complain(IOException e) throws IOException {
1122: try {
1123: rec.alert(Record.FATAL, Record.HNDSHK_FAIL);
1124: if (sSessionId != null) {
1125: Session.del(peerHost, peerPort, sSessionId);
1126: }
1127: } catch (Throwable t) {
1128: // Ignore, we are processing an exception currently
1129: }
1130:
1131: throw e;
1132: }
1133: }
1134:
1135: /**
1136: * This class implements methods to maintain resumable SSL
1137: * sessions.
1138: */
1139: // visible within the package
1140: class Session {
1141: /** Maximum number of cached resumable sessions. */
1142: private static final byte MAX_SESSIONS = 4;
1143:
1144: /**
1145: * Stores the last index where a session was overwritten, we
1146: * try to do a round-robin selection of places to overwrite
1147: */
1148: private static int delIdx = 0;
1149:
1150: /*
1151: * A session is uniquely identified by the combination of
1152: * host, port and session identifier. The master secret is
1153: * included in the cached session information.
1154: */
1155: /** Target host name. */
1156: String host;
1157: /** Target port number. */
1158: int port;
1159: /** Session identifier. */
1160: byte[] id;
1161: /** Master secret. */
1162: byte[] master;
1163: /** Target Certificate. */
1164: X509Certificate cert;
1165:
1166: /** A cache of currently resumable sessions. */
1167: private static Session[] sessions = new Session[MAX_SESSIONS];
1168:
1169: /**
1170: * Gets the master secret associated with a resumable session.
1171: * The session is uniquely identified by the combination of the
1172: * host, port.
1173: *
1174: * @param h host name of peer
1175: * @param p port number of peer
1176: *
1177: * @return matching session
1178: */
1179: static synchronized Session get(String h, int p) {
1180: for (int i = 0; i < MAX_SESSIONS; i++) {
1181: if ((sessions[i] == null) || (sessions[i].id == null))
1182: continue;
1183:
1184: if (sessions[i].host.compareTo(h) == 0
1185: && sessions[i].port == p) {
1186: return sessions[i];
1187: }
1188: }
1189:
1190: return null;
1191: }
1192:
1193: /**
1194: * Adds a new session with the specified parameters to the cache
1195: * of resumable sessions. At any given time, this class maintains
1196: * at most one resusumable session for any host/port pair.
1197: * <P />
1198: * @param h host name of peer
1199: * @param p port number of peer
1200: * @param id session identifier
1201: * @param mas master secret
1202: * @param cert certificate of peer
1203: */
1204: static synchronized void add(String h, int p, byte[] id,
1205: byte[] mas, X509Certificate cert) {
1206: // IMPL_NOTE: This will change if we stop using linear arrays
1207: int idx = MAX_SESSIONS;
1208: for (int i = 0; i < MAX_SESSIONS; i++) {
1209: if ((sessions[i] == null) || (sessions[i].id == null)) {
1210: idx = i; // possible candidate for overwriting
1211: continue;
1212: }
1213:
1214: if ((sessions[i].host.compareTo(h) == 0)
1215: && (sessions[i].port == p)) { // preferred candidate
1216: idx = i;
1217: break;
1218: }
1219: }
1220:
1221: /*
1222: * If all else is taken, overwrite the one specified by
1223: * delIdx and move delIdx over to the next one. Simulates FIFO.
1224: */
1225: if (idx == MAX_SESSIONS) {
1226: idx = delIdx;
1227: delIdx++;
1228: if (delIdx == MAX_SESSIONS)
1229: delIdx = 0;
1230: }
1231:
1232: if (sessions[idx] == null) {
1233: sessions[idx] = new Session();
1234: }
1235:
1236: sessions[idx].id = id;
1237:
1238: /*
1239: * Since the master will change after this method, we need to
1240: * copy it, to preserve its current value for later.
1241: */
1242: sessions[idx].master = new byte[mas.length];
1243: System.arraycopy(mas, 0, sessions[idx].master, 0, mas.length);
1244:
1245: sessions[idx].host = new String(h); // "h" will be a substring of URL
1246: sessions[idx].port = p;
1247: sessions[idx].cert = cert;
1248: }
1249:
1250: /**
1251: * Deletes the session identified by the specfied parameters
1252: * from the cache of resumable sessions.
1253: * <P />
1254: * @param h host name of peer
1255: * @param p port number of peer
1256: * @param sid session identifier
1257: */
1258: static synchronized void del(String h, int p, byte[] sid) {
1259: for (int i = 0; i < MAX_SESSIONS; i++) {
1260: if ((sessions[i] == null) || (sessions[i].id == null))
1261: continue;
1262:
1263: if (Utils.byteMatch(sessions[i].id, 0, sid, 0, sid.length)
1264: && (sessions[i].host.compareTo(h) == 0)
1265: && (sessions[i].port == p)) {
1266: sessions[i].id = null;
1267: sessions[i].master = null;
1268: sessions[i].host = null;
1269: sessions[i].cert = null;
1270: break;
1271: }
1272: }
1273: }
1274: }
|