Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package

sslcon.c

/* 
 * SSL v2 handshake functions, and functions common to SSL2 and SSL3.
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Netscape security libraries.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Dr Vipul Gupta <vipul.gupta@sun.com>, Sun Microsystems Laboratories
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
/* $Id: sslcon.c,v 1.39 2010/02/04 03:08:44 wtc%google.com Exp $ */

#include "nssrenam.h"
#include "cert.h"
#include "secitem.h"
#include "sechash.h"
#include "cryptohi.h"         /* for SGN_ funcs */
#include "keyhi.h"            /* for SECKEY_ high level functions. */
#include "ssl.h"
#include "sslimpl.h"
#include "sslproto.h"
#include "ssl3prot.h"
#include "sslerr.h"
#include "pk11func.h"
#include "prinit.h"
#include "prtime.h"     /* for PR_Now() */

#define XXX
static PRBool policyWasSet;

/* This ordered list is indexed by (SSL_CK_xx * 3)   */
/* Second and third bytes are MSB and LSB of master key length. */
static const PRUint8 allCipherSuites[] = {
    0,                                    0,    0,
    SSL_CK_RC4_128_WITH_MD5,              0x00, 0x80,
    SSL_CK_RC4_128_EXPORT40_WITH_MD5,           0x00, 0x80,
    SSL_CK_RC2_128_CBC_WITH_MD5,          0x00, 0x80,
    SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5, 0x00, 0x80,
    SSL_CK_IDEA_128_CBC_WITH_MD5,         0x00, 0x80,
    SSL_CK_DES_64_CBC_WITH_MD5,                 0x00, 0x40,
    SSL_CK_DES_192_EDE3_CBC_WITH_MD5,           0x00, 0xC0,
    0,                                    0,    0
};

#define ssl2_NUM_SUITES_IMPLEMENTED 6

/* This list is sent back to the client when the client-hello message 
 * contains no overlapping ciphers, so the client can report what ciphers
 * are supported by the server.  Unlike allCipherSuites (above), this list
 * is sorted by descending preference, not by cipherSuite number. 
 */
static const PRUint8 implementedCipherSuites[ssl2_NUM_SUITES_IMPLEMENTED * 3] = {
    SSL_CK_RC4_128_WITH_MD5,              0x00, 0x80,
    SSL_CK_RC2_128_CBC_WITH_MD5,          0x00, 0x80,
    SSL_CK_DES_192_EDE3_CBC_WITH_MD5,           0x00, 0xC0,
    SSL_CK_DES_64_CBC_WITH_MD5,                 0x00, 0x40,
    SSL_CK_RC4_128_EXPORT40_WITH_MD5,           0x00, 0x80,
    SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5, 0x00, 0x80
};

typedef struct ssl2SpecsStr {
    PRUint8           nkm; /* do this many hashes to generate key material. */
    PRUint8           nkd; /* size of readKey and writeKey in bytes. */
    PRUint8           blockSize;
    PRUint8           blockShift;
    CK_MECHANISM_TYPE mechanism;
    PRUint8           keyLen; /* cipher symkey size in bytes. */
    PRUint8           pubLen; /* publicly reveal this many bytes of key. */
    PRUint8           ivLen;  /* length of IV data at *ca.  */
} ssl2Specs;

static const ssl2Specs ssl_Specs[] = {
/* NONE                                 */ 
                        {  0,  0, 0, 0, },
/* SSL_CK_RC4_128_WITH_MD5          */ 
                        {  2, 16, 1, 0, CKM_RC4,       16,   0, 0, },
/* SSL_CK_RC4_128_EXPORT40_WITH_MD5 */ 
                        {  2, 16, 1, 0, CKM_RC4,       16,  11, 0, },
/* SSL_CK_RC2_128_CBC_WITH_MD5            */ 
                        {  2, 16, 8, 3, CKM_RC2_CBC,   16,   0, 8, },
/* SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5   */ 
                        {  2, 16, 8, 3, CKM_RC2_CBC,   16,  11, 8, },
/* SSL_CK_IDEA_128_CBC_WITH_MD5           */ 
                        {  0,  0, 0, 0, },
/* SSL_CK_DES_64_CBC_WITH_MD5       */ 
                        {  1,  8, 8, 3, CKM_DES_CBC,    8,   0, 8, },
/* SSL_CK_DES_192_EDE3_CBC_WITH_MD5 */ 
                        {  3, 24, 8, 3, CKM_DES3_CBC,  24,   0, 8, },
};

#define SET_ERROR_CODE    /* reminder */
#define TEST_FOR_FAILURE  /* reminder */

/*
** Put a string tag in the library so that we can examine an executable
** and see what kind of security it supports.
*/
const char *ssl_version = "SECURITY_VERSION:"
                  " +us"
                  " +export"
#ifdef TRACE
                  " +trace"
#endif
#ifdef DEBUG
                  " +debug"
#endif
                  ;

const char * const ssl_cipherName[] = {
    "unknown",
    "RC4",
    "RC4-Export",
    "RC2-CBC",
    "RC2-CBC-Export",
    "IDEA-CBC",
    "DES-CBC",
    "DES-EDE3-CBC",
    "unknown",
    "unknown", /* was fortezza, NO LONGER USED */
};


/* bit-masks, showing which SSLv2 suites are allowed.
 * lsb corresponds to first cipher suite in allCipherSuites[].
 */
static PRUint16   allowedByPolicy;          /* all off by default */
static PRUint16   maybeAllowedByPolicy;     /* all off by default */
static PRUint16   chosenPreference = 0xff;  /* all on  by default */

/* bit values for the above two bit masks */
#define SSL_CB_RC4_128_WITH_MD5              (1 << SSL_CK_RC4_128_WITH_MD5)
#define SSL_CB_RC4_128_EXPORT40_WITH_MD5     (1 << SSL_CK_RC4_128_EXPORT40_WITH_MD5)
#define SSL_CB_RC2_128_CBC_WITH_MD5          (1 << SSL_CK_RC2_128_CBC_WITH_MD5)
#define SSL_CB_RC2_128_CBC_EXPORT40_WITH_MD5 (1 << SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5)
#define SSL_CB_IDEA_128_CBC_WITH_MD5         (1 << SSL_CK_IDEA_128_CBC_WITH_MD5)
#define SSL_CB_DES_64_CBC_WITH_MD5           (1 << SSL_CK_DES_64_CBC_WITH_MD5)
#define SSL_CB_DES_192_EDE3_CBC_WITH_MD5     (1 << SSL_CK_DES_192_EDE3_CBC_WITH_MD5)
#define SSL_CB_IMPLEMENTED \
   (SSL_CB_RC4_128_WITH_MD5              | \
    SSL_CB_RC4_128_EXPORT40_WITH_MD5     | \
    SSL_CB_RC2_128_CBC_WITH_MD5          | \
    SSL_CB_RC2_128_CBC_EXPORT40_WITH_MD5 | \
    SSL_CB_DES_64_CBC_WITH_MD5           | \
    SSL_CB_DES_192_EDE3_CBC_WITH_MD5)


/* Construct a socket's list of cipher specs from the global default values.
 */
static SECStatus
ssl2_ConstructCipherSpecs(sslSocket *ss) 
{
    PRUint8 *             cs        = NULL;
    unsigned int  allowed;
    unsigned int  count;
    int           ssl3_count  = 0;
    int           final_count;
    int           i;
    SECStatus           rv;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    count = 0;
    PORT_Assert(ss != 0);
    allowed = !ss->opt.enableSSL2 ? 0 :
      (ss->allowedByPolicy & ss->chosenPreference & SSL_CB_IMPLEMENTED);
    while (allowed) {
      if (allowed & 1) 
          ++count;
      allowed >>= 1;
    }

    /* Call ssl3_config_match_init() once here, 
     * instead of inside ssl3_ConstructV2CipherSpecsHack(),
     * because the latter gets called twice below, 
     * and then again in ssl2_BeginClientHandshake().
     */
    ssl3_config_match_init(ss);

    /* ask SSL3 how many cipher suites it has. */
    rv = ssl3_ConstructV2CipherSpecsHack(ss, NULL, &ssl3_count);
    if (rv < 0) 
      return rv;
    count += ssl3_count;

    /* Allocate memory to hold cipher specs */
    if (count > 0)
      cs = (PRUint8*) PORT_Alloc(count * 3);
    else
      PORT_SetError(SSL_ERROR_SSL_DISABLED);
    if (cs == NULL)
      return SECFailure;

    if (ss->cipherSpecs != NULL) {
      PORT_Free(ss->cipherSpecs);
    }
    ss->cipherSpecs     = cs;
    ss->sizeCipherSpecs = count * 3;

    /* fill in cipher specs for SSL2 cipher suites */
    allowed = !ss->opt.enableSSL2 ? 0 :
      (ss->allowedByPolicy & ss->chosenPreference & SSL_CB_IMPLEMENTED);
    for (i = 0; i < ssl2_NUM_SUITES_IMPLEMENTED * 3; i += 3) {
      const PRUint8 * hs = implementedCipherSuites + i;
      int             ok = allowed & (1U << hs[0]);
      if (ok) {
          cs[0] = hs[0];
          cs[1] = hs[1];
          cs[2] = hs[2];
          cs += 3;
      }
    }

    /* now have SSL3 add its suites onto the end */
    rv = ssl3_ConstructV2CipherSpecsHack(ss, cs, &final_count);
    
    /* adjust for any difference between first pass and second pass */
    ss->sizeCipherSpecs -= (ssl3_count - final_count) * 3;

    return rv;
}

/* This function is called immediately after ssl2_ConstructCipherSpecs()
** at the beginning of a handshake.  It detects cases where a protocol
** (e.g. SSL2 or SSL3) is logically enabled, but all its cipher suites
** for that protocol have been disabled.  If such cases, it clears the 
** enable bit for the protocol.  If no protocols remain enabled, or
** if no cipher suites are found, it sets the error code and returns
** SECFailure, otherwise it returns SECSuccess.
*/
static SECStatus
ssl2_CheckConfigSanity(sslSocket *ss)
{
    unsigned int      allowed;
    int               ssl3CipherCount = 0;
    SECStatus         rv;

    /* count the SSL2 and SSL3 enabled ciphers.
     * if either is zero, clear the socket's enable for that protocol.
     */
    if (!ss->cipherSpecs)
      goto disabled;

    allowed = ss->allowedByPolicy & ss->chosenPreference;
    if (! allowed)
      ss->opt.enableSSL2 = PR_FALSE; /* not really enabled if no ciphers */

    /* ssl3_config_match_init was called in ssl2_ConstructCipherSpecs(). */
    /* Ask how many ssl3 CipherSuites were enabled. */
    rv = ssl3_ConstructV2CipherSpecsHack(ss, NULL, &ssl3CipherCount);
    if (rv != SECSuccess || ssl3CipherCount <= 0) {
      ss->opt.enableSSL3 = PR_FALSE; /* not really enabled if no ciphers */
      ss->opt.enableTLS  = PR_FALSE;
    }

    if (!ss->opt.enableSSL2 && !ss->opt.enableSSL3 && !ss->opt.enableTLS) {
      SSL_DBG(("%d: SSL[%d]: Can't handshake! both v2 and v3 disabled.",
             SSL_GETPID(), ss->fd));
disabled:
      PORT_SetError(SSL_ERROR_SSL_DISABLED);
      return SECFailure;
    }
    return SECSuccess;
}

/* 
 * Since this is a global (not per-socket) setting, we cannot use the
 * HandshakeLock to protect this.  Probably want a global lock.
 */
SECStatus
ssl2_SetPolicy(PRInt32 which, PRInt32 policy)
{
    PRUint32  bitMask;
    SECStatus rv       = SECSuccess;

    which &= 0x000f;
    bitMask = 1 << which;

    if (!(bitMask & SSL_CB_IMPLEMENTED)) {
      PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
      return SECFailure;
    }

    if (policy == SSL_ALLOWED) {
      allowedByPolicy   |= bitMask;
      maybeAllowedByPolicy    |= bitMask;
    } else if (policy == SSL_RESTRICTED) {
      allowedByPolicy   &= ~bitMask;
      maybeAllowedByPolicy    |= bitMask;
    } else {
      allowedByPolicy   &= ~bitMask;
      maybeAllowedByPolicy    &= ~bitMask;
    }
    allowedByPolicy           &= SSL_CB_IMPLEMENTED;
    maybeAllowedByPolicy      &= SSL_CB_IMPLEMENTED;

    policyWasSet = PR_TRUE;
    return rv;
}

SECStatus
ssl2_GetPolicy(PRInt32 which, PRInt32 *oPolicy)
{
    PRUint32     bitMask;
    PRInt32      policy;

    which &= 0x000f;
    bitMask = 1 << which;

    /* Caller assures oPolicy is not null. */
    if (!(bitMask & SSL_CB_IMPLEMENTED)) {
      PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
      *oPolicy = SSL_NOT_ALLOWED;
      return SECFailure;
    }

    if (maybeAllowedByPolicy & bitMask) {
      policy = (allowedByPolicy & bitMask) ? SSL_ALLOWED : SSL_RESTRICTED;
    } else {
      policy = SSL_NOT_ALLOWED;
    }

    *oPolicy = policy;
    return SECSuccess;
}

/* 
 * Since this is a global (not per-socket) setting, we cannot use the
 * HandshakeLock to protect this.  Probably want a global lock.
 * Called from SSL_CipherPrefSetDefault in sslsock.c
 * These changes have no effect on any sslSockets already created. 
 */
SECStatus
ssl2_CipherPrefSetDefault(PRInt32 which, PRBool enabled)
{
    PRUint32     bitMask;
    
    which &= 0x000f;
    bitMask = 1 << which;

    if (!(bitMask & SSL_CB_IMPLEMENTED)) {
      PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
      return SECFailure;
    }

    if (enabled)
      chosenPreference |= bitMask;
    else
      chosenPreference &= ~bitMask;
    chosenPreference &= SSL_CB_IMPLEMENTED;

    return SECSuccess;
}

SECStatus 
ssl2_CipherPrefGetDefault(PRInt32 which, PRBool *enabled)
{
    PRBool       rv       = PR_FALSE;
    PRUint32     bitMask;

    which &= 0x000f;
    bitMask = 1 << which;

    if (!(bitMask & SSL_CB_IMPLEMENTED)) {
      PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
      *enabled = PR_FALSE;
      return SECFailure;
    }

    rv = (PRBool)((chosenPreference & bitMask) != 0);
    *enabled = rv;
    return SECSuccess;
}

SECStatus 
ssl2_CipherPrefSet(sslSocket *ss, PRInt32 which, PRBool enabled)
{
    PRUint32     bitMask;
    
    which &= 0x000f;
    bitMask = 1 << which;

    if (!(bitMask & SSL_CB_IMPLEMENTED)) {
      PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
      return SECFailure;
    }

    if (enabled)
      ss->chosenPreference |= bitMask;
    else
      ss->chosenPreference &= ~bitMask;
    ss->chosenPreference &= SSL_CB_IMPLEMENTED;

    return SECSuccess;
}

SECStatus 
ssl2_CipherPrefGet(sslSocket *ss, PRInt32 which, PRBool *enabled)
{
    PRBool       rv       = PR_FALSE;
    PRUint32     bitMask;

    which &= 0x000f;
    bitMask = 1 << which;

    if (!(bitMask & SSL_CB_IMPLEMENTED)) {
      PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
      *enabled = PR_FALSE;
      return SECFailure;
    }

    rv = (PRBool)((ss->chosenPreference & bitMask) != 0);
    *enabled = rv;
    return SECSuccess;
}


/* copy global default policy into socket. */
void      
ssl2_InitSocketPolicy(sslSocket *ss)
{
    ss->allowedByPolicy       = allowedByPolicy;
    ss->maybeAllowedByPolicy  = maybeAllowedByPolicy;
    ss->chosenPreference      = chosenPreference;
}


/************************************************************************/

/* Called from ssl2_CreateSessionCypher(), which already holds handshake lock.
 */
static SECStatus
ssl2_CreateMAC(sslSecurityInfo *sec, SECItem *readKey, SECItem *writeKey, 
          int cipherChoice)
{
    switch (cipherChoice) {

      case SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5:
      case SSL_CK_RC2_128_CBC_WITH_MD5:
      case SSL_CK_RC4_128_EXPORT40_WITH_MD5:
      case SSL_CK_RC4_128_WITH_MD5:
      case SSL_CK_DES_64_CBC_WITH_MD5:
      case SSL_CK_DES_192_EDE3_CBC_WITH_MD5:
      sec->hash = HASH_GetHashObject(HASH_AlgMD5);
      SECITEM_CopyItem(0, &sec->sendSecret, writeKey);
      SECITEM_CopyItem(0, &sec->rcvSecret, readKey);
      break;

      default:
      PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
      return SECFailure;
    }
    sec->hashcx = (*sec->hash->create)();
    if (sec->hashcx == NULL)
      return SECFailure;
    return SECSuccess;
}

/************************************************************************
 * All the Send functions below must acquire and release the socket's 
 * xmitBufLock.
 */

/* Called from all the Send* functions below. */
static SECStatus
ssl2_GetSendBuffer(sslSocket *ss, unsigned int len)
{
    SECStatus rv = SECSuccess;

    PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));

    if (len < 128) {
      len = 128;
    }
    if (len > ss->sec.ci.sendBuf.space) {
      rv = sslBuffer_Grow(&ss->sec.ci.sendBuf, len);
      if (rv != SECSuccess) {
          SSL_DBG(("%d: SSL[%d]: ssl2_GetSendBuffer failed, tried to get %d bytes",
                 SSL_GETPID(), ss->fd, len));
          rv = SECFailure;
      }
    }
    return rv;
}

/* Called from:
 * ssl2_ClientSetupSessionCypher() <- ssl2_HandleServerHelloMessage()
 * ssl2_HandleRequestCertificate()     <- ssl2_HandleMessage() <- 
                              ssl_Do1stHandshake()
 * ssl2_HandleMessage()                <- ssl_Do1stHandshake()
 * ssl2_HandleServerHelloMessage() <- ssl_Do1stHandshake()
                                     after ssl2_BeginClientHandshake()
 * ssl2_RestartHandshakeAfterCertReq() <- Called from certdlgs.c in nav.
 * ssl2_HandleClientHelloMessage() <- ssl_Do1stHandshake() 
                                     after ssl2_BeginServerHandshake()
 * 
 * Acquires and releases the socket's xmitBufLock.
 */   
int
ssl2_SendErrorMessage(sslSocket *ss, int error)
{
    int rv;
    PRUint8 msg[SSL_HL_ERROR_HBYTES];

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    msg[0] = SSL_MT_ERROR;
    msg[1] = MSB(error);
    msg[2] = LSB(error);

    ssl_GetXmitBufLock(ss);    /***************************************/

    SSL_TRC(3, ("%d: SSL[%d]: sending error %d", SSL_GETPID(), ss->fd, error));

    ss->handshakeBegun = 1;
    rv = (*ss->sec.send)(ss, msg, sizeof(msg), 0);
    if (rv >= 0) {
      rv = SECSuccess;
    }
    ssl_ReleaseXmitBufLock(ss);    /***************************************/
    return rv;
}

/* Called from ssl2_TryToFinish().  
 * Acquires and releases the socket's xmitBufLock.
 */
static SECStatus
ssl2_SendClientFinishedMessage(sslSocket *ss)
{
    SECStatus        rv    = SECSuccess;
    int              sent;
    PRUint8    msg[1 + SSL_CONNECTIONID_BYTES];

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    ssl_GetXmitBufLock(ss);    /***************************************/

    if (ss->sec.ci.sentFinished == 0) {
      ss->sec.ci.sentFinished = 1;

      SSL_TRC(3, ("%d: SSL[%d]: sending client-finished",
                SSL_GETPID(), ss->fd));

      msg[0] = SSL_MT_CLIENT_FINISHED;
      PORT_Memcpy(msg+1, ss->sec.ci.connectionID, 
                  sizeof(ss->sec.ci.connectionID));

      DUMP_MSG(29, (ss, msg, 1 + sizeof(ss->sec.ci.connectionID)));
      sent = (*ss->sec.send)(ss, msg, 1 + sizeof(ss->sec.ci.connectionID), 0);
      rv = (sent >= 0) ? SECSuccess : (SECStatus)sent;
    }
    ssl_ReleaseXmitBufLock(ss);    /***************************************/
    return rv;
}

/* Called from 
 * ssl2_HandleClientSessionKeyMessage() <- ssl2_HandleClientHelloMessage()
 * ssl2_HandleClientHelloMessage()  <- ssl_Do1stHandshake() 
                                      after ssl2_BeginServerHandshake()
 * Acquires and releases the socket's xmitBufLock.
 */
static SECStatus
ssl2_SendServerVerifyMessage(sslSocket *ss)
{
    PRUint8 *        msg;
    int              sendLen;
    int              sent;
    SECStatus        rv;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    ssl_GetXmitBufLock(ss);    /***************************************/

    sendLen = 1 + SSL_CHALLENGE_BYTES;
    rv = ssl2_GetSendBuffer(ss, sendLen);
    if (rv != SECSuccess) {
      goto done;
    }

    msg = ss->sec.ci.sendBuf.buf;
    msg[0] = SSL_MT_SERVER_VERIFY;
    PORT_Memcpy(msg+1, ss->sec.ci.clientChallenge, SSL_CHALLENGE_BYTES);

    DUMP_MSG(29, (ss, msg, sendLen));
    sent = (*ss->sec.send)(ss, msg, sendLen, 0);

    rv = (sent >= 0) ? SECSuccess : (SECStatus)sent;

done:
    ssl_ReleaseXmitBufLock(ss);    /***************************************/
    return rv;
}

/* Called from ssl2_TryToFinish(). 
 * Acquires and releases the socket's xmitBufLock.
 */
static SECStatus
ssl2_SendServerFinishedMessage(sslSocket *ss)
{
    sslSessionID *   sid;
    PRUint8 *        msg;
    int              sendLen, sent;
    SECStatus        rv    = SECSuccess;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    ssl_GetXmitBufLock(ss);    /***************************************/

    if (ss->sec.ci.sentFinished == 0) {
      ss->sec.ci.sentFinished = 1;
      PORT_Assert(ss->sec.ci.sid != 0);
      sid = ss->sec.ci.sid;

      SSL_TRC(3, ("%d: SSL[%d]: sending server-finished",
                SSL_GETPID(), ss->fd));

      sendLen = 1 + sizeof(sid->u.ssl2.sessionID);
      rv = ssl2_GetSendBuffer(ss, sendLen);
      if (rv != SECSuccess) {
          goto done;
      }

      msg = ss->sec.ci.sendBuf.buf;
      msg[0] = SSL_MT_SERVER_FINISHED;
      PORT_Memcpy(msg+1, sid->u.ssl2.sessionID,
                sizeof(sid->u.ssl2.sessionID));

      DUMP_MSG(29, (ss, msg, sendLen));
      sent = (*ss->sec.send)(ss, msg, sendLen, 0);

      if (sent < 0) {
          /* If send failed, it is now a bogus  session-id */
          (*ss->sec.uncache)(sid);
          rv = (SECStatus)sent;
      } else if (!ss->opt.noCache) {
          /* Put the sid in session-id cache, (may already be there) */
          (*ss->sec.cache)(sid);
          rv = SECSuccess;
      }
      ssl_FreeSID(sid);
      ss->sec.ci.sid = 0;
    }
done:
    ssl_ReleaseXmitBufLock(ss);    /***************************************/
    return rv;
}

/* Called from ssl2_ClientSetupSessionCypher() <- 
 *                                  ssl2_HandleServerHelloMessage() 
 *                                           after ssl2_BeginClientHandshake()
 * Acquires and releases the socket's xmitBufLock.
 */
static SECStatus
ssl2_SendSessionKeyMessage(sslSocket *ss, int cipher, int keySize,
                  PRUint8 *ca, int caLen,
                  PRUint8 *ck, int ckLen,
                  PRUint8 *ek, int ekLen)
{
    PRUint8 *        msg;
    int              sendLen;
    int              sent;
    SECStatus        rv;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    ssl_GetXmitBufLock(ss);    /***************************************/

    sendLen = SSL_HL_CLIENT_MASTER_KEY_HBYTES + ckLen + ekLen + caLen;
    rv = ssl2_GetSendBuffer(ss, sendLen);
    if (rv != SECSuccess) 
      goto done;

    SSL_TRC(3, ("%d: SSL[%d]: sending client-session-key",
            SSL_GETPID(), ss->fd));

    msg = ss->sec.ci.sendBuf.buf;
    msg[0] = SSL_MT_CLIENT_MASTER_KEY;
    msg[1] = cipher;
    msg[2] = MSB(keySize);
    msg[3] = LSB(keySize);
    msg[4] = MSB(ckLen);
    msg[5] = LSB(ckLen);
    msg[6] = MSB(ekLen);
    msg[7] = LSB(ekLen);
    msg[8] = MSB(caLen);
    msg[9] = LSB(caLen);
    PORT_Memcpy(msg+SSL_HL_CLIENT_MASTER_KEY_HBYTES, ck, ckLen);
    PORT_Memcpy(msg+SSL_HL_CLIENT_MASTER_KEY_HBYTES+ckLen, ek, ekLen);
    PORT_Memcpy(msg+SSL_HL_CLIENT_MASTER_KEY_HBYTES+ckLen+ekLen, ca, caLen);

    DUMP_MSG(29, (ss, msg, sendLen));
    sent = (*ss->sec.send)(ss, msg, sendLen, 0);
    rv = (sent >= 0) ? SECSuccess : (SECStatus)sent;
done:
    ssl_ReleaseXmitBufLock(ss);    /***************************************/
    return rv;
}

/* Called from ssl2_TriggerNextMessage() <- ssl2_HandleMessage() 
 * Acquires and releases the socket's xmitBufLock.
 */
static SECStatus
ssl2_SendCertificateRequestMessage(sslSocket *ss)
{
    PRUint8 *        msg;
    int              sent;
    int              sendLen;
    SECStatus        rv;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    ssl_GetXmitBufLock(ss);    /***************************************/

    sendLen = SSL_HL_REQUEST_CERTIFICATE_HBYTES + SSL_CHALLENGE_BYTES;
    rv = ssl2_GetSendBuffer(ss, sendLen);
    if (rv != SECSuccess) 
      goto done;

    SSL_TRC(3, ("%d: SSL[%d]: sending certificate request",
            SSL_GETPID(), ss->fd));

    /* Generate random challenge for client to encrypt */
    PK11_GenerateRandom(ss->sec.ci.serverChallenge, SSL_CHALLENGE_BYTES);

    msg = ss->sec.ci.sendBuf.buf;
    msg[0] = SSL_MT_REQUEST_CERTIFICATE;
    msg[1] = SSL_AT_MD5_WITH_RSA_ENCRYPTION;
    PORT_Memcpy(msg + SSL_HL_REQUEST_CERTIFICATE_HBYTES, 
                ss->sec.ci.serverChallenge, SSL_CHALLENGE_BYTES);

    DUMP_MSG(29, (ss, msg, sendLen));
    sent = (*ss->sec.send)(ss, msg, sendLen, 0);
    rv = (sent >= 0) ? SECSuccess : (SECStatus)sent;
done:
    ssl_ReleaseXmitBufLock(ss);    /***************************************/
    return rv;
}

/* Called from ssl2_HandleRequestCertificate() <- ssl2_HandleMessage()
 *             ssl2_RestartHandshakeAfterCertReq() <- (application)
 * Acquires and releases the socket's xmitBufLock.
 */
static int
ssl2_SendCertificateResponseMessage(sslSocket *ss, SECItem *cert, 
                                    SECItem *encCode)
{
    PRUint8 *msg;
    int rv, sendLen;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    ssl_GetXmitBufLock(ss);    /***************************************/

    sendLen = SSL_HL_CLIENT_CERTIFICATE_HBYTES + encCode->len + cert->len;
    rv = ssl2_GetSendBuffer(ss, sendLen);
    if (rv) 
      goto done;

    SSL_TRC(3, ("%d: SSL[%d]: sending certificate response",
            SSL_GETPID(), ss->fd));

    msg = ss->sec.ci.sendBuf.buf;
    msg[0] = SSL_MT_CLIENT_CERTIFICATE;
    msg[1] = SSL_CT_X509_CERTIFICATE;
    msg[2] = MSB(cert->len);
    msg[3] = LSB(cert->len);
    msg[4] = MSB(encCode->len);
    msg[5] = LSB(encCode->len);
    PORT_Memcpy(msg + SSL_HL_CLIENT_CERTIFICATE_HBYTES, cert->data, cert->len);
    PORT_Memcpy(msg + SSL_HL_CLIENT_CERTIFICATE_HBYTES + cert->len,
            encCode->data, encCode->len);

    DUMP_MSG(29, (ss, msg, sendLen));
    rv = (*ss->sec.send)(ss, msg, sendLen, 0);
    if (rv >= 0) {
      rv = SECSuccess;
    }
done:
    ssl_ReleaseXmitBufLock(ss);    /***************************************/
    return rv;
}

/********************************************************************
**  Send functions above this line must aquire & release the socket's   
**    xmitBufLock.  
** All the ssl2_Send functions below this line are called vis ss->sec.send
**    and require that the caller hold the xmitBufLock.
*/

/*
** Called from ssl2_SendStream, ssl2_SendBlock, but not from ssl2_SendClear.
*/
static SECStatus
ssl2_CalcMAC(PRUint8             * result, 
           sslSecurityInfo     * sec,
           const PRUint8       * data, 
           unsigned int          dataLen,
           unsigned int          paddingLen)
{
    const PRUint8 *      secret           = sec->sendSecret.data;
    unsigned int         secretLen  = sec->sendSecret.len;
    unsigned long        sequenceNumber = sec->sendSequence;
    unsigned int         nout;
    PRUint8              seq[4];
    PRUint8              padding[32];/* XXX max blocksize? */

    if (!sec->hash || !sec->hash->length)
      return SECSuccess;
    if (!sec->hashcx)
      return SECFailure;

    /* Reset hash function */
    (*sec->hash->begin)(sec->hashcx);

    /* Feed hash the data */
    (*sec->hash->update)(sec->hashcx, secret, secretLen);
    (*sec->hash->update)(sec->hashcx, data, dataLen);
    PORT_Memset(padding, paddingLen, paddingLen);
    (*sec->hash->update)(sec->hashcx, padding, paddingLen);

    seq[0] = (PRUint8) (sequenceNumber >> 24);
    seq[1] = (PRUint8) (sequenceNumber >> 16);
    seq[2] = (PRUint8) (sequenceNumber >> 8);
    seq[3] = (PRUint8) (sequenceNumber);

    PRINT_BUF(60, (0, "calc-mac secret:", secret, secretLen));
    PRINT_BUF(60, (0, "calc-mac data:", data, dataLen));
    PRINT_BUF(60, (0, "calc-mac padding:", padding, paddingLen));
    PRINT_BUF(60, (0, "calc-mac seq:", seq, 4));

    (*sec->hash->update)(sec->hashcx, seq, 4);

    /* Get result */
    (*sec->hash->end)(sec->hashcx, result, &nout, sec->hash->length);

    return SECSuccess;
}

/*
** Maximum transmission amounts. These are tiny bit smaller than they
** need to be (they account for the MAC length plus some padding),
** assuming the MAC is 16 bytes long and the padding is a max of 7 bytes
** long. This gives an additional 9 bytes of slop to work within.
*/
#define MAX_STREAM_CYPHER_LEN 0x7fe0
#define MAX_BLOCK_CYPHER_LEN  0x3fe0

/*
** Send some data in the clear. 
** Package up data with the length header and send it.
**
** Return count of bytes successfully written, or negative number (failure).
*/
static PRInt32 
ssl2_SendClear(sslSocket *ss, const PRUint8 *in, PRInt32 len, PRInt32 flags)
{
    PRUint8         * out;
    int               rv;
    int               amount;
    int               count   = 0;

    PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );

    SSL_TRC(10, ("%d: SSL[%d]: sending %d bytes in the clear",
             SSL_GETPID(), ss->fd, len));
    PRINT_BUF(50, (ss, "clear data:", (PRUint8*) in, len));

    while (len) {
      amount = PR_MIN( len, MAX_STREAM_CYPHER_LEN );
      if (amount + 2 > ss->sec.writeBuf.space) {
          rv = sslBuffer_Grow(&ss->sec.writeBuf, amount + 2);
          if (rv != SECSuccess) {
            count = rv;
            break;
          }
      }
      out = ss->sec.writeBuf.buf;

      /*
      ** Construct message.
      */
      out[0] = 0x80 | MSB(amount);
      out[1] = LSB(amount);
      PORT_Memcpy(&out[2], in, amount);

      /* Now send the data */
      rv = ssl_DefSend(ss, out, amount + 2, flags & ~ssl_SEND_FLAG_MASK);
      if (rv < 0) {
          if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) {
            rv = 0;
          } else {
            /* Return short write if some data already went out... */
            if (count == 0)
                count = rv;
            break;
          }
      }

      if ((unsigned)rv < (amount + 2)) {
          /* Short write.  Save the data and return. */
          if (ssl_SaveWriteData(ss, out + rv, amount + 2 - rv) 
              == SECFailure) {
            count = SECFailure;
          } else {
            count += amount;
            ss->sec.sendSequence++;
          }
          break;
      }

      ss->sec.sendSequence++;
      in    += amount;
      count += amount;
      len   -= amount;
    }

    return count;
}

/*
** Send some data, when using a stream cipher. Stream ciphers have a
** block size of 1. Package up the data with the length header
** and send it.
*/
static PRInt32 
ssl2_SendStream(sslSocket *ss, const PRUint8 *in, PRInt32 len, PRInt32 flags)
{
    PRUint8       *  out;
    int              rv;
    int              count    = 0;

    int              amount;
    PRUint8          macLen;
    int              nout;
    int              buflen;

    PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );

    SSL_TRC(10, ("%d: SSL[%d]: sending %d bytes using stream cipher",
             SSL_GETPID(), ss->fd, len));
    PRINT_BUF(50, (ss, "clear data:", (PRUint8*) in, len));

    while (len) {
      ssl_GetSpecReadLock(ss);  /*************************************/

      macLen = ss->sec.hash->length;
      amount = PR_MIN( len, MAX_STREAM_CYPHER_LEN );
      buflen = amount + 2 + macLen;
      if (buflen > ss->sec.writeBuf.space) {
          rv = sslBuffer_Grow(&ss->sec.writeBuf, buflen);
          if (rv != SECSuccess) {
            goto loser;
          }
      }
      out    = ss->sec.writeBuf.buf;
      nout   = amount + macLen;
      out[0] = 0x80 | MSB(nout);
      out[1] = LSB(nout);

      /* Calculate MAC */
      rv = ssl2_CalcMAC(out+2,            /* put MAC here */
                        &ss->sec, 
                      in, amount,         /* input addr & length */
                    0);                   /* no padding */
      if (rv != SECSuccess) 
          goto loser;

      /* Encrypt MAC */
      rv = (*ss->sec.enc)(ss->sec.writecx, out+2, &nout, macLen, out+2, macLen);
      if (rv) goto loser;

      /* Encrypt data from caller */
      rv = (*ss->sec.enc)(ss->sec.writecx, out+2+macLen, &nout, amount, in, amount);
      if (rv) goto loser;

      ssl_ReleaseSpecReadLock(ss);  /*************************************/

      PRINT_BUF(50, (ss, "encrypted data:", out, buflen));

      rv = ssl_DefSend(ss, out, buflen, flags & ~ssl_SEND_FLAG_MASK);
      if (rv < 0) {
          if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) {
            SSL_TRC(50, ("%d: SSL[%d]: send stream would block, "
                       "saving data", SSL_GETPID(), ss->fd));
            rv = 0;
          } else {
            SSL_TRC(10, ("%d: SSL[%d]: send stream error %d",
                       SSL_GETPID(), ss->fd, PORT_GetError()));
            /* Return short write if some data already went out... */
            if (count == 0)
                count = rv;
            goto done;
          }
      }

      if ((unsigned)rv < buflen) {
          /* Short write.  Save the data and return. */
          if (ssl_SaveWriteData(ss, out + rv, buflen - rv) == SECFailure) {
            count = SECFailure;
          } else {
            count += amount;
            ss->sec.sendSequence++;
          }
          goto done;
      }

      ss->sec.sendSequence++;
      in    += amount;
      count += amount;
      len   -= amount;
    }

done:
    return count;

loser:
    ssl_ReleaseSpecReadLock(ss);
    return SECFailure;
}

/*
** Send some data, when using a block cipher. Package up the data with
** the length header and send it.
*/
/* XXX assumes blocksize is > 7 */
static PRInt32
ssl2_SendBlock(sslSocket *ss, const PRUint8 *in, PRInt32 len, PRInt32 flags)
{
    PRUint8       *  out;               /* begining of output buffer.    */
    PRUint8       *  op;                /* next output byte goes here.   */
    int              rv;                /* value from funcs we called.   */
    int              count    = 0;        /* this function's return value. */

    unsigned int     hlen;              /* output record hdr len, 2 or 3 */
    unsigned int     macLen;            /* MAC is this many bytes long.  */
    int              amount;            /* of plaintext to go in record. */
    unsigned int     padding;           /* add this many padding byte.   */
    int              nout;              /* ciphertext size after header. */
    int              buflen;            /* size of generated record.     */

    PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );

    SSL_TRC(10, ("%d: SSL[%d]: sending %d bytes using block cipher",
             SSL_GETPID(), ss->fd, len));
    PRINT_BUF(50, (ss, "clear data:", in, len));

    while (len) {
      ssl_GetSpecReadLock(ss);  /*************************************/

      macLen = ss->sec.hash->length;
      /* Figure out how much to send, including mac and padding */
      amount  = PR_MIN( len, MAX_BLOCK_CYPHER_LEN );
      nout    = amount + macLen;
      padding = nout & (ss->sec.blockSize - 1);
      if (padding) {
          hlen    = 3;
          padding = ss->sec.blockSize - padding;
          nout   += padding;
      } else {
          hlen = 2;
      }
      buflen = hlen + nout;
      if (buflen > ss->sec.writeBuf.space) {
          rv = sslBuffer_Grow(&ss->sec.writeBuf, buflen);
          if (rv != SECSuccess) {
            goto loser;
          }
      }
      out = ss->sec.writeBuf.buf;

      /* Construct header */
      op = out;
      if (padding) {
          *op++ = MSB(nout);
          *op++ = LSB(nout);
          *op++ = padding;
      } else {
          *op++ = 0x80 | MSB(nout);
          *op++ = LSB(nout);
      }

      /* Calculate MAC */
      rv = ssl2_CalcMAC(op,         /* MAC goes here. */
                        &ss->sec, 
                      in, amount,   /* intput addr, len */
                    padding);
      if (rv != SECSuccess) 
          goto loser;
      op += macLen;

      /* Copy in the input data */
      /* XXX could eliminate the copy by folding it into the encryption */
      PORT_Memcpy(op, in, amount);
      op += amount;
      if (padding) {
          PORT_Memset(op, padding, padding);
          op += padding;
      }

      /* Encrypt result */
      rv = (*ss->sec.enc)(ss->sec.writecx, out+hlen, &nout, buflen-hlen,
                   out+hlen, op - (out + hlen));
      if (rv) 
          goto loser;

      ssl_ReleaseSpecReadLock(ss);  /*************************************/

      PRINT_BUF(50, (ss, "final xmit data:", out, op - out));

      rv = ssl_DefSend(ss, out, op - out, flags & ~ssl_SEND_FLAG_MASK);
      if (rv < 0) {
          if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) {
            rv = 0;
          } else {
            SSL_TRC(10, ("%d: SSL[%d]: send block error %d",
                       SSL_GETPID(), ss->fd, PORT_GetError()));
            /* Return short write if some data already went out... */
            if (count == 0)
                count = rv;
            goto done;
          }
      }

      if (rv < (op - out)) {
          /* Short write.  Save the data and return. */
          if (ssl_SaveWriteData(ss, out + rv, op - out - rv) == SECFailure) {
            count = SECFailure;
          } else {
            count += amount;
            ss->sec.sendSequence++;
          }
          goto done;
      }

      ss->sec.sendSequence++;
      in    += amount;
      count += amount;
      len   -= amount;
    }

done:
    return count;

loser:
    ssl_ReleaseSpecReadLock(ss);
    return SECFailure;
}

/*
** Called from: ssl2_HandleServerHelloMessage,
**              ssl2_HandleClientSessionKeyMessage,
**              ssl2_RestartHandshakeAfterServerCert,
**              ssl2_HandleClientHelloMessage,
**              
*/
static void
ssl2_UseEncryptedSendFunc(sslSocket *ss)
{
    ssl_GetXmitBufLock(ss);
    PORT_Assert(ss->sec.hashcx != 0);

    ss->gs.encrypted = 1;
    ss->sec.send = (ss->sec.blockSize > 1) ? ssl2_SendBlock : ssl2_SendStream;
    ssl_ReleaseXmitBufLock(ss);
}

/* Called while initializing socket in ssl_CreateSecurityInfo().
** This function allows us to keep the name of ssl2_SendClear static.
*/
void
ssl2_UseClearSendFunc(sslSocket *ss)
{
    ss->sec.send = ssl2_SendClear;
}

/************************************************************************
**                END of Send functions.                          * 
*************************************************************************/

/***********************************************************************
 * For SSL3, this gathers in and handles records/messages until either 
 *    the handshake is complete or application data is available.
 *
 * For SSL2, this gathers in only the next SSLV2 record.
 *
 * Called from ssl_Do1stHandshake() via function pointer ss->handshake.
 * Caller must hold handshake lock.
 * This function acquires and releases the RecvBufLock.
 *
 * returns SECSuccess for success.
 * returns SECWouldBlock when that value is returned by ssl2_GatherRecord() or
 *    ssl3_GatherCompleteHandshake().
 * returns SECFailure on all other errors.
 *
 * The gather functions called by ssl_GatherRecord1stHandshake are expected 
 *    to return values interpreted as follows:
 *  1 : the function completed without error.
 *  0 : the function read EOF.
 * -1 : read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error.
 * -2 : the function wants ssl_GatherRecord1stHandshake to be called again 
 *    immediately, by ssl_Do1stHandshake.
 *
 * This code is similar to, and easily confused with, DoRecv() in sslsecur.c
 *
 * This function is called from ssl_Do1stHandshake().  
 * The following functions put ssl_GatherRecord1stHandshake into ss->handshake:
 *    ssl2_HandleMessage
 *    ssl2_HandleVerifyMessage
 *    ssl2_HandleServerHelloMessage
 *    ssl2_BeginClientHandshake     
 *    ssl2_HandleClientSessionKeyMessage
 *    ssl2_RestartHandshakeAfterCertReq 
 *    ssl3_RestartHandshakeAfterCertReq 
 *    ssl2_RestartHandshakeAfterServerCert 
 *    ssl3_RestartHandshakeAfterServerCert 
 *    ssl2_HandleClientHelloMessage
 *    ssl2_BeginServerHandshake
 */
SECStatus
ssl_GatherRecord1stHandshake(sslSocket *ss)
{
    int rv;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    ssl_GetRecvBufLock(ss);

    if (ss->version >= SSL_LIBRARY_VERSION_3_0) {
      /* Wait for handshake to complete, or application data to arrive.  */
      rv = ssl3_GatherCompleteHandshake(ss, 0);
    } else {
      /* See if we have a complete record */
      rv = ssl2_GatherRecord(ss, 0);
    }
    SSL_TRC(10, ("%d: SSL[%d]: handshake gathering, rv=%d",
             SSL_GETPID(), ss->fd, rv));

    ssl_ReleaseRecvBufLock(ss);

    if (rv <= 0) {
      if (rv == SECWouldBlock) {
          /* Progress is blocked waiting for callback completion.  */
          SSL_TRC(10, ("%d: SSL[%d]: handshake blocked (need %d)",
                   SSL_GETPID(), ss->fd, ss->gs.remainder));
          return SECWouldBlock;
      }
      if (rv == 0) {
          /* EOF. Loser  */
          PORT_SetError(PR_END_OF_FILE_ERROR);
      }
      return SECFailure;      /* rv is < 0 here. */
    }

    SSL_TRC(10, ("%d: SSL[%d]: got handshake record of %d bytes",
             SSL_GETPID(), ss->fd, ss->gs.recordLen));

    ss->handshake = 0;  /* makes ssl_Do1stHandshake call ss->nextHandshake.*/
    return SECSuccess;
}

/************************************************************************/

/* Called from ssl2_ServerSetupSessionCypher()
 *             ssl2_ClientSetupSessionCypher()
 */
static SECStatus
ssl2_FillInSID(sslSessionID * sid, 
          int            cipher,
        PRUint8       *keyData, 
        int            keyLen,
        PRUint8       *ca, 
        int            caLen,
        int            keyBits, 
        int            secretKeyBits,
        SSLSignType    authAlgorithm,
        PRUint32       authKeyBits,
        SSLKEAType     keaType,
        PRUint32       keaKeyBits)
{
    PORT_Assert(sid->references == 1);
    PORT_Assert(sid->cached == never_cached);
    PORT_Assert(sid->u.ssl2.masterKey.data == 0);
    PORT_Assert(sid->u.ssl2.cipherArg.data == 0);

    sid->version = SSL_LIBRARY_VERSION_2;

    sid->u.ssl2.cipherType = cipher;
    sid->u.ssl2.masterKey.data = (PRUint8*) PORT_Alloc(keyLen);
    if (!sid->u.ssl2.masterKey.data) {
      return SECFailure;
    }
    PORT_Memcpy(sid->u.ssl2.masterKey.data, keyData, keyLen);
    sid->u.ssl2.masterKey.len = keyLen;
    sid->u.ssl2.keyBits       = keyBits;
    sid->u.ssl2.secretKeyBits = secretKeyBits;
    sid->authAlgorithm        = authAlgorithm;
    sid->authKeyBits          = authKeyBits;
    sid->keaType              = keaType;
    sid->keaKeyBits           = keaKeyBits;
    sid->lastAccessTime = sid->creationTime = ssl_Time();
    sid->expirationTime = sid->creationTime + ssl_sid_timeout;

    if (caLen) {
      sid->u.ssl2.cipherArg.data = (PRUint8*) PORT_Alloc(caLen);
      if (!sid->u.ssl2.cipherArg.data) {
          return SECFailure;
      }
      sid->u.ssl2.cipherArg.len = caLen;
      PORT_Memcpy(sid->u.ssl2.cipherArg.data, ca, caLen);
    }
    return SECSuccess;
}

/*
** Construct session keys given the masterKey (tied to the session-id),
** the client's challenge and the server's nonce.
**
** Called from ssl2_CreateSessionCypher() <-
*/
static SECStatus
ssl2_ProduceKeys(sslSocket *    ss, 
            SECItem *      readKey, 
          SECItem *      writeKey,
          SECItem *      masterKey, 
          PRUint8 *      challenge,
          PRUint8 *      nonce, 
          int            cipherType)
{
    PK11Context * cx        = 0;
    unsigned      nkm       = 0; /* number of hashes to generate key mat. */
    unsigned      nkd       = 0; /* size of readKey and writeKey. */
    unsigned      part;
    unsigned      i;
    unsigned      off;
    SECStatus     rv;
    PRUint8       countChar;
    PRUint8       km[3*16];   /* buffer for key material. */

    readKey->data = 0;
    writeKey->data = 0;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    rv = SECSuccess;
    cx = PK11_CreateDigestContext(SEC_OID_MD5);
    if (cx == NULL) {
      ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
      return SECFailure;
    }

    nkm = ssl_Specs[cipherType].nkm;
    nkd = ssl_Specs[cipherType].nkd;

    readKey->data = (PRUint8*) PORT_Alloc(nkd);
    if (!readKey->data) 
      goto loser;
    readKey->len = nkd;

    writeKey->data = (PRUint8*) PORT_Alloc(nkd);
    if (!writeKey->data) 
      goto loser;
    writeKey->len = nkd;

    /* Produce key material */
    countChar = '0';
    for (i = 0, off = 0; i < nkm; i++, off += 16) {
      rv  = PK11_DigestBegin(cx);
      rv |= PK11_DigestOp(cx, masterKey->data, masterKey->len);
      rv |= PK11_DigestOp(cx, &countChar,      1);
      rv |= PK11_DigestOp(cx, challenge,       SSL_CHALLENGE_BYTES);
      rv |= PK11_DigestOp(cx, nonce,           SSL_CONNECTIONID_BYTES);
      rv |= PK11_DigestFinal(cx, km+off, &part, MD5_LENGTH);
      if (rv != SECSuccess) {
          ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
          rv = SECFailure;
          goto loser;
      }
      countChar++;
    }

    /* Produce keys */
    PORT_Memcpy(readKey->data,  km,       nkd);
    PORT_Memcpy(writeKey->data, km + nkd, nkd);

loser:
    PK11_DestroyContext(cx, PR_TRUE);
    return rv;
}

/* Called from ssl2_ServerSetupSessionCypher() 
**                  <- ssl2_HandleClientSessionKeyMessage()
**                          <- ssl2_HandleClientHelloMessage()
** and from    ssl2_ClientSetupSessionCypher() 
**                  <- ssl2_HandleServerHelloMessage()
*/
static SECStatus
ssl2_CreateSessionCypher(sslSocket *ss, sslSessionID *sid, PRBool isClient)
{
    SECItem         * rk = NULL;
    SECItem         * wk = NULL;
    SECItem *         param;
    SECStatus         rv;
    int               cipherType  = sid->u.ssl2.cipherType;
    PK11SlotInfo *    slot        = NULL;
    CK_MECHANISM_TYPE mechanism;
    SECItem           readKey;
    SECItem           writeKey;

    void *readcx = 0;
    void *writecx = 0;
    readKey.data = 0;
    writeKey.data = 0;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
    if((ss->sec.ci.sid == 0))
      goto sec_loser;   /* don't crash if asserts are off */

    /* Trying to cut down on all these switch statements that should be tables.
     * So, test cipherType once, here, and then use tables below. 
     */
    switch (cipherType) {
    case SSL_CK_RC4_128_EXPORT40_WITH_MD5:
    case SSL_CK_RC4_128_WITH_MD5:
    case SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5:
    case SSL_CK_RC2_128_CBC_WITH_MD5:
    case SSL_CK_DES_64_CBC_WITH_MD5:
    case SSL_CK_DES_192_EDE3_CBC_WITH_MD5:
      break;

    default:
      SSL_DBG(("%d: SSL[%d]: ssl2_CreateSessionCypher: unknown cipher=%d",
             SSL_GETPID(), ss->fd, cipherType));
      PORT_SetError(isClient ? SSL_ERROR_BAD_SERVER : SSL_ERROR_BAD_CLIENT);
      goto sec_loser;
    }
 
    rk = isClient ? &readKey  : &writeKey;
    wk = isClient ? &writeKey : &readKey;

    /* Produce the keys for this session */
    rv = ssl2_ProduceKeys(ss, &readKey, &writeKey, &sid->u.ssl2.masterKey,
                 ss->sec.ci.clientChallenge, ss->sec.ci.connectionID,
                 cipherType);
    if (rv != SECSuccess) 
      goto loser;
    PRINT_BUF(7, (ss, "Session read-key: ", rk->data, rk->len));
    PRINT_BUF(7, (ss, "Session write-key: ", wk->data, wk->len));

    PORT_Memcpy(ss->sec.ci.readKey, readKey.data, readKey.len);
    PORT_Memcpy(ss->sec.ci.writeKey, writeKey.data, writeKey.len);
    ss->sec.ci.keySize = readKey.len;

    /* Setup the MAC */
    rv = ssl2_CreateMAC(&ss->sec, rk, wk, cipherType);
    if (rv != SECSuccess) 
      goto loser;

    /* First create the session key object */
    SSL_TRC(3, ("%d: SSL[%d]: using %s", SSL_GETPID(), ss->fd,
          ssl_cipherName[cipherType]));


    mechanism  = ssl_Specs[cipherType].mechanism;

    /* set destructer before we call loser... */
    ss->sec.destroy = (void (*)(void*, PRBool)) PK11_DestroyContext;
    slot = PK11_GetBestSlot(mechanism, ss->pkcs11PinArg);
    if (slot == NULL)
      goto loser;

    param = PK11_ParamFromIV(mechanism, &sid->u.ssl2.cipherArg);
    if (param == NULL)
      goto loser;
    readcx = PK11_CreateContextByRawKey(slot, mechanism, PK11_OriginUnwrap,
                              CKA_DECRYPT, rk, param,
                              ss->pkcs11PinArg);
    SECITEM_FreeItem(param, PR_TRUE);
    if (readcx == NULL)
      goto loser;

    /* build the client context */
    param = PK11_ParamFromIV(mechanism, &sid->u.ssl2.cipherArg);
    if (param == NULL)
      goto loser;
    writecx = PK11_CreateContextByRawKey(slot, mechanism, PK11_OriginUnwrap,
                               CKA_ENCRYPT, wk, param,
                               ss->pkcs11PinArg);
    SECITEM_FreeItem(param,PR_TRUE);
    if (writecx == NULL)
      goto loser;
    PK11_FreeSlot(slot);

    rv = SECSuccess;
    ss->sec.enc           = (SSLCipher) PK11_CipherOp;
    ss->sec.dec           = (SSLCipher) PK11_CipherOp;
    ss->sec.readcx        = (void *) readcx;
    ss->sec.writecx       = (void *) writecx;
    ss->sec.blockSize     = ssl_Specs[cipherType].blockSize;
    ss->sec.blockShift    = ssl_Specs[cipherType].blockShift;
    ss->sec.cipherType    = sid->u.ssl2.cipherType;
    ss->sec.keyBits       = sid->u.ssl2.keyBits;
    ss->sec.secretKeyBits = sid->u.ssl2.secretKeyBits;
    goto done;

  loser:
    if (ss->sec.destroy) {
      if (readcx)  (*ss->sec.destroy)(readcx, PR_TRUE);
      if (writecx) (*ss->sec.destroy)(writecx, PR_TRUE);
    }
    ss->sec.destroy = NULL;
    if (slot) PK11_FreeSlot(slot);

  sec_loser:
    rv = SECFailure;

  done:
    if (rk) {
      SECITEM_ZfreeItem(rk, PR_FALSE);
    }
    if (wk) {
      SECITEM_ZfreeItem(wk, PR_FALSE);
    }
    return rv;
}

/*
** Setup the server ciphers given information from a CLIENT-MASTER-KEY
** message.
**    "ss"      pointer to the ssl-socket object
**    "cipher"  the cipher type to use
**    "keyBits" the size of the final cipher key
**    "ck"      the clear-key data
**    "ckLen"   the number of bytes of clear-key data
**    "ek"      the encrypted-key data
**    "ekLen"   the number of bytes of encrypted-key data
**    "ca"      the cipher-arg data
**    "caLen"   the number of bytes of cipher-arg data
**
** The MASTER-KEY is constructed by first decrypting the encrypted-key
** data. This produces the SECRET-KEY-DATA. The MASTER-KEY is composed by
** concatenating the clear-key data with the SECRET-KEY-DATA. This code
** checks to make sure that the client didn't send us an improper amount
** of SECRET-KEY-DATA (it restricts the length of that data to match the
** spec).
**
** Called from ssl2_HandleClientSessionKeyMessage().
*/
static SECStatus
ssl2_ServerSetupSessionCypher(sslSocket *ss, int cipher, unsigned int keyBits,
                   PRUint8 *ck, unsigned int ckLen,
                   PRUint8 *ek, unsigned int ekLen,
                   PRUint8 *ca, unsigned int caLen)
{
    PRUint8      *    dk   = NULL; /* decrypted master key */
    sslSessionID *    sid;
    sslServerCerts *  sc   = ss->serverCerts + kt_rsa;
    PRUint8       *   kbuf = 0;     /* buffer for RSA decrypted data. */
    unsigned int      ddLen;  /* length of RSA decrypted data in kbuf */
    unsigned int      keySize;
    unsigned int      dkLen;    /* decrypted key length in bytes */
    int               modulusLen;
    SECStatus         rv;
    PRUint16          allowed;  /* cipher kinds enabled and allowed by policy */
    PRUint8           mkbuf[SSL_MAX_MASTER_KEY_BYTES];

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
    PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss)   );
    PORT_Assert((sc->SERVERKEY != 0));
    PORT_Assert((ss->sec.ci.sid != 0));
    sid = ss->sec.ci.sid;

    /* Trying to cut down on all these switch statements that should be tables.
     * So, test cipherType once, here, and then use tables below. 
     */
    switch (cipher) {
    case SSL_CK_RC4_128_EXPORT40_WITH_MD5:
    case SSL_CK_RC4_128_WITH_MD5:
    case SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5:
    case SSL_CK_RC2_128_CBC_WITH_MD5:
    case SSL_CK_DES_64_CBC_WITH_MD5:
    case SSL_CK_DES_192_EDE3_CBC_WITH_MD5:
      break;

    default:
      SSL_DBG(("%d: SSL[%d]: ssl2_ServerSetupSessionCypher: unknown cipher=%d",
             SSL_GETPID(), ss->fd, cipher));
      PORT_SetError(SSL_ERROR_BAD_CLIENT);
      goto loser;
    }

    allowed = ss->allowedByPolicy & ss->chosenPreference & SSL_CB_IMPLEMENTED;
    if (!(allowed & (1 << cipher))) {
      /* client chose a kind we don't allow! */
      SSL_DBG(("%d: SSL[%d]: disallowed cipher=%d",
             SSL_GETPID(), ss->fd, cipher));
      PORT_SetError(SSL_ERROR_BAD_CLIENT);
      goto loser;
    }

    keySize = ssl_Specs[cipher].keyLen;
    if (keyBits != keySize * BPB) {
      SSL_DBG(("%d: SSL[%d]: invalid master secret key length=%d (bits)!",
             SSL_GETPID(), ss->fd, keyBits));
      PORT_SetError(SSL_ERROR_BAD_CLIENT);
      goto loser;
    }

    if (ckLen != ssl_Specs[cipher].pubLen) {
      SSL_DBG(("%d: SSL[%d]: invalid clear key length, ckLen=%d (bytes)!",
             SSL_GETPID(), ss->fd, ckLen));
      PORT_SetError(SSL_ERROR_BAD_CLIENT);
      goto loser;
    }

    if (caLen != ssl_Specs[cipher].ivLen) {
      SSL_DBG(("%d: SSL[%d]: invalid key args length, caLen=%d (bytes)!",
             SSL_GETPID(), ss->fd, caLen));
      PORT_SetError(SSL_ERROR_BAD_CLIENT);
      goto loser;
    }

    modulusLen = PK11_GetPrivateModulusLen(sc->SERVERKEY);
    if (modulusLen == -1) {
      /* XXX If the key is bad, then PK11_PubDecryptRaw will fail below. */
      modulusLen = ekLen;
    }
    if (ekLen > modulusLen || ekLen + ckLen < keySize) {
      SSL_DBG(("%d: SSL[%d]: invalid encrypted key length, ekLen=%d (bytes)!",
             SSL_GETPID(), ss->fd, ekLen));
      PORT_SetError(SSL_ERROR_BAD_CLIENT);
      goto loser;
    }

    /* allocate the buffer to hold the decrypted portion of the key. */
    kbuf = (PRUint8*)PORT_Alloc(modulusLen);
    if (!kbuf) {
      goto loser;
    }
    dkLen = keySize - ckLen;
    dk    = kbuf + modulusLen - dkLen;

    /* Decrypt encrypted half of the key. 
    ** NOTE: PK11_PubDecryptRaw will barf on a non-RSA key. This is
    ** desired behavior here.
    */
    rv = PK11_PubDecryptRaw(sc->SERVERKEY, kbuf, &ddLen, modulusLen, ek, ekLen);
    if (rv != SECSuccess) 
      goto hide_loser;

    /* Is the length of the decrypted data (ddLen) the expected value? */
    if (modulusLen != ddLen) 
      goto hide_loser;

    /* Cheaply verify that PKCS#1 was used to format the encryption block */
    if ((kbuf[0] != 0x00) || (kbuf[1] != 0x02) || (dk[-1] != 0x00)) {
      SSL_DBG(("%d: SSL[%d]: strange encryption block",
             SSL_GETPID(), ss->fd));
      PORT_SetError(SSL_ERROR_BAD_CLIENT);
      goto hide_loser;
    }

    /* Make sure we're not subject to a version rollback attack. */
    if (ss->opt.enableSSL3 || ss->opt.enableTLS) {
      static const PRUint8 threes[8] = { 0x03, 0x03, 0x03, 0x03,
                                     0x03, 0x03, 0x03, 0x03 };
      
      if (PORT_Memcmp(dk - 8 - 1, threes, 8) == 0) {
          PORT_SetError(SSL_ERROR_BAD_CLIENT);
          goto hide_loser;
      }
    }
    if (0) {
hide_loser:
      /* Defense against the Bleichenbacher attack.
       * Provide the client with NO CLUES that the decrypted master key
       * was erroneous.  Don't send any error messages.
       * Instead, Generate a completely bogus master key .
       */
      PK11_GenerateRandom(dk, dkLen);
    }

    /*
    ** Construct master key out of the pieces.
    */
    if (ckLen) {
      PORT_Memcpy(mkbuf, ck, ckLen);
    }
    PORT_Memcpy(mkbuf + ckLen, dk, dkLen);

    /* Fill in session-id */
    rv = ssl2_FillInSID(sid, cipher, mkbuf, keySize, ca, caLen,
               keyBits, keyBits - (ckLen<<3),
               ss->sec.authAlgorithm, ss->sec.authKeyBits,
               ss->sec.keaType,       ss->sec.keaKeyBits);
    if (rv != SECSuccess) {
      goto loser;
    }

    /* Create session ciphers */
    rv = ssl2_CreateSessionCypher(ss, sid, PR_FALSE);
    if (rv != SECSuccess) {
      goto loser;
    }

    SSL_TRC(1, ("%d: SSL[%d]: server, using %s cipher, clear=%d total=%d",
            SSL_GETPID(), ss->fd, ssl_cipherName[cipher],
            ckLen<<3, keySize<<3));
    rv = SECSuccess;
    goto done;

  loser:
    rv = SECFailure;

  done:
    PORT_Free(kbuf);
    return rv;
}

/************************************************************************/

/*
** Rewrite the incoming cipher specs, comparing to list of specs we support,
** (ss->cipherSpecs) and eliminating anything we don't support
**
*  Note: Our list may contain SSL v3 ciphers.  
*  We MUST NOT match on any of those.  
*  Fortunately, this is easy to detect because SSLv3 ciphers have zero
*  in the first byte, and none of the SSLv2 ciphers do.
*
*  Called from ssl2_HandleClientHelloMessage().
*  Returns the number of bytes of "qualified cipher specs", 
*  which is typically a multiple of 3, but will be zero if there are none.
*/
static int
ssl2_QualifyCypherSpecs(sslSocket *ss, 
                        PRUint8 *  cs, /* cipher specs in client hello msg. */
                    int        csLen)
{
    PRUint8 *    ms;
    PRUint8 *    hs;
    PRUint8 *    qs;
    int          mc;
    int          hc;
    PRUint8      qualifiedSpecs[ssl2_NUM_SUITES_IMPLEMENTED * 3];

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
    PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss)   );

    if (!ss->cipherSpecs) {
      SECStatus rv = ssl2_ConstructCipherSpecs(ss);
      if (rv != SECSuccess || !ss->cipherSpecs) 
          return 0;
    }

    PRINT_BUF(10, (ss, "specs from client:", cs, csLen));
    qs = qualifiedSpecs;
    ms = ss->cipherSpecs;
    for (mc = ss->sizeCipherSpecs; mc > 0; mc -= 3, ms += 3) {
      if (ms[0] == 0)
          continue;
      for (hs = cs, hc = csLen; hc > 0; hs += 3, hc -= 3) {
          if ((hs[0] == ms[0]) &&
            (hs[1] == ms[1]) &&
            (hs[2] == ms[2])) {
            /* Copy this cipher spec into the "keep" section */
            qs[0] = hs[0];
            qs[1] = hs[1];
            qs[2] = hs[2];
            qs   += 3;
            break;
          }
      }
    }
    hc = qs - qualifiedSpecs;
    PRINT_BUF(10, (ss, "qualified specs from client:", qualifiedSpecs, hc));
    PORT_Memcpy(cs, qualifiedSpecs, hc);
    return hc;
}

/*
** Pick the best cipher we can find, given the array of server cipher
** specs.  Returns cipher number (e.g. SSL_CK_*), or -1 for no overlap.
** If successful, stores the master key size (bytes) in *pKeyLen.
**
** This is correct only for the client side, but presently
** this function is only called from 
**    ssl2_ClientSetupSessionCypher() <- ssl2_HandleServerHelloMessage()
**
** Note that most servers only return a single cipher suite in their 
** ServerHello messages.  So, the code below for finding the "best" cipher
** suite usually has only one choice.  The client and server should send 
** their cipher suite lists sorted in descending order by preference.
*/
static int
ssl2_ChooseSessionCypher(sslSocket *ss, 
                         int        hc,    /* number of cs's in hs. */
                     PRUint8 *  hs,    /* server hello's cipher suites. */
                     int *      pKeyLen) /* out: sym key size in bytes. */
{
    PRUint8 *       ms;
    unsigned int    i;
    int             bestKeySize;
    int             bestRealKeySize;
    int             bestCypher;
    int             keySize;
    int             realKeySize;
    PRUint8 *       ohs               = hs;
    const PRUint8 * preferred;
    static const PRUint8 noneSuch[3] = { 0, 0, 0 };

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
    PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss)   );

    if (!ss->cipherSpecs) {
      SECStatus rv = ssl2_ConstructCipherSpecs(ss);
      if (rv != SECSuccess || !ss->cipherSpecs) 
          goto loser;
    }

    if (!ss->preferredCipher) {
      unsigned int allowed = ss->allowedByPolicy & ss->chosenPreference &
                             SSL_CB_IMPLEMENTED;
      if (allowed) {
          preferred = implementedCipherSuites;
          for (i = ssl2_NUM_SUITES_IMPLEMENTED; i > 0; --i) {
            if (0 != (allowed & (1U << preferred[0]))) {
                ss->preferredCipher = preferred;
                break;
            }
            preferred += 3;
          }
      }
    }
    preferred = ss->preferredCipher ? ss->preferredCipher : noneSuch;
    /*
    ** Scan list of ciphers recieved from peer and look for a match in
    ** our list.  
    *  Note: Our list may contain SSL v3 ciphers.  
    *  We MUST NOT match on any of those.  
    *  Fortunately, this is easy to detect because SSLv3 ciphers have zero
    *  in the first byte, and none of the SSLv2 ciphers do.
    */
    bestKeySize = bestRealKeySize = 0;
    bestCypher = -1;
    while (--hc >= 0) {
      for (i = 0, ms = ss->cipherSpecs; i < ss->sizeCipherSpecs; i += 3, ms += 3) {
          if ((hs[0] == preferred[0]) &&
            (hs[1] == preferred[1]) &&
            (hs[2] == preferred[2]) &&
             hs[0] != 0) {
            /* Pick this cipher immediately! */
            *pKeyLen = (((hs[1] << 8) | hs[2]) + 7) >> 3;
            return hs[0];
          }
          if ((hs[0] == ms[0]) && (hs[1] == ms[1]) && (hs[2] == ms[2]) &&
               hs[0] != 0) {
            /* Found a match */

            /* Use secret keySize to determine which cipher is best */
            realKeySize = (hs[1] << 8) | hs[2];
            switch (hs[0]) {
              case SSL_CK_RC4_128_EXPORT40_WITH_MD5:
              case SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5:
                keySize = 40;
                break;
              default:
                keySize = realKeySize;
                break;
            }
            if (keySize > bestKeySize) {
                bestCypher = hs[0];
                bestKeySize = keySize;
                bestRealKeySize = realKeySize;
            }
          }
      }
      hs += 3;
    }
    if (bestCypher < 0) {
      /*
      ** No overlap between server and client. Re-examine server list
      ** to see what kind of ciphers it does support so that we can set
      ** the error code appropriately.
      */
      if ((ohs[0] == SSL_CK_RC4_128_WITH_MD5) ||
          (ohs[0] == SSL_CK_RC2_128_CBC_WITH_MD5)) {
          PORT_SetError(SSL_ERROR_US_ONLY_SERVER);
      } else if ((ohs[0] == SSL_CK_RC4_128_EXPORT40_WITH_MD5) ||
               (ohs[0] == SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5)) {
          PORT_SetError(SSL_ERROR_EXPORT_ONLY_SERVER);
      } else {
          PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
      }
      SSL_DBG(("%d: SSL[%d]: no cipher overlap", SSL_GETPID(), ss->fd));
      goto loser;
    }
    *pKeyLen = (bestRealKeySize + 7) >> 3;
    return bestCypher;

  loser:
    return -1;
}

static SECStatus
ssl2_ClientHandleServerCert(sslSocket *ss, PRUint8 *certData, int certLen)
{
    CERTCertificate *cert      = NULL;
    SECItem          certItem;

    certItem.data = certData;
    certItem.len  = certLen;

    /* decode the certificate */
    cert = CERT_NewTempCertificate(ss->dbHandle, &certItem, NULL,
                           PR_FALSE, PR_TRUE);
    
    if (cert == NULL) {
      SSL_DBG(("%d: SSL[%d]: decode of server certificate fails",
             SSL_GETPID(), ss->fd));
      PORT_SetError(SSL_ERROR_BAD_CERTIFICATE);
      return SECFailure;
    }

#ifdef TRACE
    {
      if (ssl_trace >= 1) {
          char *issuer;
          char *subject;
          issuer = CERT_NameToAscii(&cert->issuer);
          subject = CERT_NameToAscii(&cert->subject);
          SSL_TRC(1,("%d: server certificate issuer: '%s'",
                   SSL_GETPID(), issuer ? issuer : "OOPS"));
          SSL_TRC(1,("%d: server name: '%s'",
                   SSL_GETPID(), subject ? subject : "OOPS"));
          PORT_Free(issuer);
          PORT_Free(subject);
      }
    }
#endif

    ss->sec.peerCert = cert;
    return SECSuccess;
}


/*
 * Format one block of data for public/private key encryption using
 * the rules defined in PKCS #1. SSL2 does this itself to handle the
 * rollback detection.
 */
#define RSA_BLOCK_MIN_PAD_LEN           8
#define RSA_BLOCK_FIRST_OCTET           0x00
#define RSA_BLOCK_AFTER_PAD_OCTET       0x00
#define RSA_BLOCK_PUBLIC_OCTET            0x02
unsigned char *
ssl_FormatSSL2Block(unsigned modulusLen, SECItem *data)
{
    unsigned char *block;
    unsigned char *bp;
    int padLen;
    SECStatus rv;
    int i;

    if (modulusLen < data->len + (3 + RSA_BLOCK_MIN_PAD_LEN)) {
      PORT_SetError(SEC_ERROR_BAD_KEY);
      return NULL;
    }
    block = (unsigned char *) PORT_Alloc(modulusLen);
    if (block == NULL)
      return NULL;

    bp = block;

    /*
     * All RSA blocks start with two octets:
     *      0x00 || BlockType
     */
    *bp++ = RSA_BLOCK_FIRST_OCTET;
    *bp++ = RSA_BLOCK_PUBLIC_OCTET;

    /*
     * 0x00 || BT || Pad || 0x00 || ActualData
     *   1      1   padLen    1      data->len
     * Pad is all non-zero random bytes.
     */
    padLen = modulusLen - data->len - 3;
    PORT_Assert (padLen >= RSA_BLOCK_MIN_PAD_LEN);
    rv = PK11_GenerateRandom(bp, padLen);
    if (rv == SECFailure) goto loser;
    /* replace all the 'zero' bytes */
    for (i = 0; i < padLen; i++) {
      while (bp[i] == RSA_BLOCK_AFTER_PAD_OCTET) {
          rv = PK11_GenerateRandom(bp+i, 1);
          if (rv == SECFailure) goto loser;
      }
    }
    bp += padLen;
    *bp++ = RSA_BLOCK_AFTER_PAD_OCTET;
    PORT_Memcpy (bp, data->data, data->len);

    return block;
loser:
    if (block) PORT_Free(block);
    return NULL;
}

/*
** Given the server's public key and cipher specs, generate a session key
** that is ready to use for encrypting/decrypting the byte stream. At
** the same time, generate the SSL_MT_CLIENT_MASTER_KEY message and
** send it to the server.
**
** Called from ssl2_HandleServerHelloMessage()
*/
static SECStatus
ssl2_ClientSetupSessionCypher(sslSocket *ss, PRUint8 *cs, int csLen)
{
    sslSessionID *    sid;
    PRUint8 *         ca;     /* points to iv data, or NULL if none. */
    PRUint8 *         ekbuf         = 0;
    CERTCertificate * cert          = 0;
    SECKEYPublicKey * serverKey     = 0;
    unsigned          modulusLen    = 0;
    SECStatus         rv;
    int               cipher;
    int               keyLen; /* cipher symkey size in bytes. */
    int               ckLen;  /* publicly reveal this many bytes of key. */
    int               caLen;  /* length of IV data at *ca.  */
    int               nc;

    unsigned char *eblock;    /* holds unencrypted PKCS#1 formatted key. */
    SECItem           rek;    /* holds portion of symkey to be encrypted. */

    PRUint8           keyData[SSL_MAX_MASTER_KEY_BYTES];
    PRUint8           iv     [8];

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    eblock = NULL;

    sid = ss->sec.ci.sid;
    PORT_Assert(sid != 0);

    cert = ss->sec.peerCert;
    
    serverKey = CERT_ExtractPublicKey(cert);
    if (!serverKey) {
      SSL_DBG(("%d: SSL[%d]: extract public key failed: error=%d",
             SSL_GETPID(), ss->fd, PORT_GetError()));
      PORT_SetError(SSL_ERROR_BAD_CERTIFICATE);
      rv = SECFailure;
      goto loser2;
    }

    ss->sec.authAlgorithm = ssl_sign_rsa;
    ss->sec.keaType       = ssl_kea_rsa;
    ss->sec.keaKeyBits    = \
    ss->sec.authKeyBits   = SECKEY_PublicKeyStrengthInBits(serverKey);

    /* Choose a compatible cipher with the server */
    nc = csLen / 3;
    cipher = ssl2_ChooseSessionCypher(ss, nc, cs, &keyLen);
    if (cipher < 0) {
      /* ssl2_ChooseSessionCypher has set error code. */
      ssl2_SendErrorMessage(ss, SSL_PE_NO_CYPHERS);
      goto loser;
    }

    /* Generate the random keys */
    PK11_GenerateRandom(keyData, sizeof(keyData));

    /*
    ** Next, carve up the keys into clear and encrypted portions. The
    ** clear data is taken from the start of keyData and the encrypted
    ** portion from the remainder. Note that each of these portions is
    ** carved in half, one half for the read-key and one for the
    ** write-key.
    */
    ca = 0;

    /* We know that cipher is a legit value here, because 
     * ssl2_ChooseSessionCypher doesn't return bogus values.
     */
    ckLen = ssl_Specs[cipher].pubLen;     /* cleartext key length. */
    caLen = ssl_Specs[cipher].ivLen;      /* IV length.           */
    if (caLen) {
      PORT_Assert(sizeof iv >= caLen);
      PK11_GenerateRandom(iv, caLen);
      ca = iv;
    }

    /* Fill in session-id */
    rv = ssl2_FillInSID(sid, cipher, keyData, keyLen,
               ca, caLen, keyLen << 3, (keyLen - ckLen) << 3,
               ss->sec.authAlgorithm, ss->sec.authKeyBits,
               ss->sec.keaType,       ss->sec.keaKeyBits);
    if (rv != SECSuccess) {
      goto loser;
    }

    SSL_TRC(1, ("%d: SSL[%d]: client, using %s cipher, clear=%d total=%d",
            SSL_GETPID(), ss->fd, ssl_cipherName[cipher],
            ckLen<<3, keyLen<<3));

    /* Now setup read and write ciphers */
    rv = ssl2_CreateSessionCypher(ss, sid, PR_TRUE);
    if (rv != SECSuccess) {
      goto loser;
    }

    /*
    ** Fill in the encryption buffer with some random bytes. Then 
    ** copy in the portion of the session key we are encrypting.
    */
    modulusLen = SECKEY_PublicKeyStrength(serverKey);
    rek.data   = keyData + ckLen;
    rek.len    = keyLen  - ckLen;
    eblock = ssl_FormatSSL2Block(modulusLen, &rek);
    if (eblock == NULL) 
      goto loser;

    /* Set up the padding for version 2 rollback detection. */
    /* XXX We should really use defines here */
    if (ss->opt.enableSSL3 || ss->opt.enableTLS) {
      PORT_Assert((modulusLen - rek.len) > 12);
      PORT_Memset(eblock + modulusLen - rek.len - 8 - 1, 0x03, 8);
    }
    ekbuf = (PRUint8*) PORT_Alloc(modulusLen);
    if (!ekbuf) 
      goto loser;
    PRINT_BUF(10, (ss, "master key encryption block:",
               eblock, modulusLen));

    /* Encrypt ekitem */
    rv = PK11_PubEncryptRaw(serverKey, ekbuf, eblock, modulusLen,
                                    ss->pkcs11PinArg);
    if (rv) 
      goto loser;

    /*  Now we have everything ready to send */
    rv = ssl2_SendSessionKeyMessage(ss, cipher, keyLen << 3, ca, caLen,
                         keyData, ckLen, ekbuf, modulusLen);
    if (rv != SECSuccess) {
      goto loser;
    }
    rv = SECSuccess;
    goto done;

  loser:
    rv = SECFailure;

  loser2:
  done:
    PORT_Memset(keyData, 0, sizeof(keyData));
    PORT_ZFree(ekbuf, modulusLen);
    PORT_ZFree(eblock, modulusLen);
    SECKEY_DestroyPublicKey(serverKey);
    return rv;
}

/************************************************************************/

/* 
 * Called from ssl2_HandleMessage in response to SSL_MT_SERVER_FINISHED message.
 * Caller holds recvBufLock and handshakeLock
 */
static void
ssl2_ClientRegSessionID(sslSocket *ss, PRUint8 *s)
{
    sslSessionID *sid = ss->sec.ci.sid;

    /* Record entry in nonce cache */
    if (sid->peerCert == NULL) {
      PORT_Memcpy(sid->u.ssl2.sessionID, s, sizeof(sid->u.ssl2.sessionID));
      sid->peerCert = CERT_DupCertificate(ss->sec.peerCert);

    }
    if (!ss->opt.noCache)
      (*ss->sec.cache)(sid);
}

/* Called from ssl2_HandleMessage() */
static SECStatus
ssl2_TriggerNextMessage(sslSocket *ss)
{
    SECStatus        rv;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    if ((ss->sec.ci.requiredElements & CIS_HAVE_CERTIFICATE) &&
      !(ss->sec.ci.sentElements & CIS_HAVE_CERTIFICATE)) {
      ss->sec.ci.sentElements |= CIS_HAVE_CERTIFICATE;
      rv = ssl2_SendCertificateRequestMessage(ss);
      return rv;
    }
    return SECSuccess;
}

/* See if it's time to send our finished message, or if the handshakes are
** complete.  Send finished message if appropriate.
** Returns SECSuccess unless anything goes wrong.
**
** Called from ssl2_HandleMessage,
**             ssl2_HandleVerifyMessage 
**             ssl2_HandleServerHelloMessage
**             ssl2_HandleClientSessionKeyMessage
**             ssl2_RestartHandshakeAfterCertReq
**             ssl2_RestartHandshakeAfterServerCert
*/
static SECStatus
ssl2_TryToFinish(sslSocket *ss)
{
    SECStatus        rv;
    char             e, ef;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    e = ss->sec.ci.elements;
    ef = e | CIS_HAVE_FINISHED;
    if ((ef & ss->sec.ci.requiredElements) == ss->sec.ci.requiredElements) {
      if (ss->sec.isServer) {
          /* Send server finished message if we already didn't */
          rv = ssl2_SendServerFinishedMessage(ss);
      } else {
          /* Send client finished message if we already didn't */
          rv = ssl2_SendClientFinishedMessage(ss);
      }
      if (rv != SECSuccess) {
          return rv;
      }
      if ((e & ss->sec.ci.requiredElements) == ss->sec.ci.requiredElements) {
          /* Totally finished */
          ss->handshake = 0;
          return SECSuccess;
      }
    }
    return SECSuccess;
}

/*
** Called from ssl2_HandleRequestCertificate
**             ssl2_RestartHandshakeAfterCertReq
*/
static SECStatus
ssl2_SignResponse(sslSocket *ss,
           SECKEYPrivateKey *key,
           SECItem *response)
{
    SGNContext *     sgn = NULL;
    PRUint8 *        challenge;
    unsigned int     len;
    SECStatus        rv       = SECFailure;
    
    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    challenge = ss->sec.ci.serverChallenge;
    len = ss->sec.ci.serverChallengeLen;
    
    /* Sign the expected data... */
    sgn = SGN_NewContext(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION,key);
    if (!sgn) 
      goto done;
    rv = SGN_Begin(sgn);
    if (rv != SECSuccess) 
      goto done;
    rv = SGN_Update(sgn, ss->sec.ci.readKey, ss->sec.ci.keySize);
    if (rv != SECSuccess) 
      goto done;
    rv = SGN_Update(sgn, ss->sec.ci.writeKey, ss->sec.ci.keySize);
    if (rv != SECSuccess) 
      goto done;
    rv = SGN_Update(sgn, challenge, len);
    if (rv != SECSuccess) 
      goto done;
    rv = SGN_Update(sgn, ss->sec.peerCert->derCert.data, 
                         ss->sec.peerCert->derCert.len);
    if (rv != SECSuccess) 
      goto done;
    rv = SGN_End(sgn, response);
    if (rv != SECSuccess) 
      goto done;

done:
    SGN_DestroyContext(sgn, PR_TRUE);
    return rv == SECSuccess ? SECSuccess : SECFailure;
}

/*
** Try to handle a request-certificate message. Get client's certificate
** and private key and sign a message for the server to see.
** Caller must hold handshakeLock 
**
** Called from ssl2_HandleMessage().
*/
static int
ssl2_HandleRequestCertificate(sslSocket *ss)
{
    CERTCertificate * cert    = NULL;     /* app-selected client cert. */
    SECKEYPrivateKey *key     = NULL;     /* priv key for cert. */
    SECStatus         rv;
    SECItem           response;
    int               ret     = 0;
    PRUint8           authType;


    /*
     * These things all need to be initialized before we can "goto loser".
     */
    response.data = NULL;

    /* get challenge info from connectionInfo */
    authType = ss->sec.ci.authType;

    if (authType != SSL_AT_MD5_WITH_RSA_ENCRYPTION) {
      SSL_TRC(7, ("%d: SSL[%d]: unsupported auth type 0x%x", SSL_GETPID(),
                ss->fd, authType));
      goto no_cert_error;
    }

    /* Get certificate and private-key from client */
    if (!ss->getClientAuthData) {
      SSL_TRC(7, ("%d: SSL[%d]: client doesn't support client-auth",
                SSL_GETPID(), ss->fd));
      goto no_cert_error;
    }
    ret = (*ss->getClientAuthData)(ss->getClientAuthDataArg, ss->fd,
                           NULL, &cert, &key);
    if ( ret == SECWouldBlock ) {
      ssl_SetAlwaysBlock(ss);
      goto done;
    }

    if (ret) {
      goto no_cert_error;
    }

    /* check what the callback function returned */
    if ((!cert) || (!key)) {
        /* we are missing either the key or cert */
        if (cert) {
            /* got a cert, but no key - free it */
            CERT_DestroyCertificate(cert);
            cert = NULL;
        }
        if (key) {
            /* got a key, but no cert - free it */
            SECKEY_DestroyPrivateKey(key);
            key = NULL;
        }
        goto no_cert_error;
    }

    rv = ssl2_SignResponse(ss, key, &response);
    if ( rv != SECSuccess ) {
      ret = -1;
      goto loser;
    }

    /* Send response message */
    ret = ssl2_SendCertificateResponseMessage(ss, &cert->derCert, &response);

    /* Now, remember the cert we sent. But first, forget any previous one. */
    if (ss->sec.localCert) {
      CERT_DestroyCertificate(ss->sec.localCert);
    }
    ss->sec.localCert = CERT_DupCertificate(cert);
    PORT_Assert(!ss->sec.ci.sid->localCert);
    if (ss->sec.ci.sid->localCert) {
      CERT_DestroyCertificate(ss->sec.ci.sid->localCert);
    }
    ss->sec.ci.sid->localCert = cert;
    cert = NULL;

    goto done;

  no_cert_error:
    SSL_TRC(7, ("%d: SSL[%d]: no certificate (ret=%d)", SSL_GETPID(),
            ss->fd, ret));
    ret = ssl2_SendErrorMessage(ss, SSL_PE_NO_CERTIFICATE);

  loser:
  done:
    if ( cert ) {
      CERT_DestroyCertificate(cert);
    }
    if ( key ) {
      SECKEY_DestroyPrivateKey(key);
    }
    if ( response.data ) {
      PORT_Free(response.data);
    }
    
    return ret;
}

/*
** Called from ssl2_HandleMessage for SSL_MT_CLIENT_CERTIFICATE message.
** Caller must hold HandshakeLock and RecvBufLock, since cd and response
** are contained in the gathered input data.
*/
static SECStatus
ssl2_HandleClientCertificate(sslSocket *    ss, 
                             PRUint8        certType, /* XXX unused */
                       PRUint8 *      cd, 
                       unsigned int   cdLen,
                       PRUint8 *      response,
                       unsigned int   responseLen)
{
    CERTCertificate *cert     = NULL;
    SECKEYPublicKey *pubKey   = NULL;
    VFYContext *     vfy      = NULL;
    SECItem *        derCert;
    SECStatus        rv       = SECFailure;
    SECItem          certItem;
    SECItem          rep;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
    PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss)   );

    /* Extract the certificate */
    certItem.data = cd;
    certItem.len  = cdLen;

    cert = CERT_NewTempCertificate(ss->dbHandle, &certItem, NULL,
                           PR_FALSE, PR_TRUE);
    if (cert == NULL) {
      goto loser;
    }

    /* save the certificate, since the auth routine will need it */
    ss->sec.peerCert = cert;

    /* Extract the public key */
    pubKey = CERT_ExtractPublicKey(cert);
    if (!pubKey) 
      goto loser;
    
    /* Verify the response data... */
    rep.data = response;
    rep.len = responseLen;
    /* SSL 2.0 only supports RSA certs, so we don't have to worry about
     * DSA here. */
    vfy = VFY_CreateContext(pubKey, &rep, SEC_OID_PKCS1_RSA_ENCRYPTION,
                      ss->pkcs11PinArg);
    if (!vfy) 
      goto loser;
    rv = VFY_Begin(vfy);
    if (rv) 
      goto loser;

    rv = VFY_Update(vfy, ss->sec.ci.readKey, ss->sec.ci.keySize);
    if (rv) 
      goto loser;
    rv = VFY_Update(vfy, ss->sec.ci.writeKey, ss->sec.ci.keySize);
    if (rv) 
      goto loser;
    rv = VFY_Update(vfy, ss->sec.ci.serverChallenge, SSL_CHALLENGE_BYTES);
    if (rv) 
      goto loser;

    derCert = &ss->serverCerts[kt_rsa].serverCert->derCert;
    rv = VFY_Update(vfy, derCert->data, derCert->len);
    if (rv) 
      goto loser;
    rv = VFY_End(vfy);
    if (rv) 
      goto loser;

    /* Now ask the server application if it likes the certificate... */
    rv = (SECStatus) (*ss->authCertificate)(ss->authCertificateArg,
                                  ss->fd, PR_TRUE, PR_TRUE);
    /* Hey, it liked it. */
    if (SECSuccess == rv) 
      goto done;

loser:
    ss->sec.peerCert = NULL;
    CERT_DestroyCertificate(cert);

done:
    VFY_DestroyContext(vfy, PR_TRUE);
    SECKEY_DestroyPublicKey(pubKey);
    return rv;
}

/*
** Handle remaining messages between client/server. Process finished
** messages from either side and any authentication requests.
** This should only be called for SSLv2 handshake messages, 
** not for application data records.
** Caller must hold handshake lock.
**
** Called from ssl_Do1stHandshake().
** 
*/
static SECStatus
ssl2_HandleMessage(sslSocket *ss)
{
    PRUint8 *        data;
    PRUint8 *        cid;
    unsigned         len, certType, certLen, responseLen;
    int              rv;
    int              rv2;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    ssl_GetRecvBufLock(ss);

    data = ss->gs.buf.buf + ss->gs.recordOffset;

    if (ss->gs.recordLen < 1) {
      goto bad_peer;
    }
    SSL_TRC(3, ("%d: SSL[%d]: received %d message",
            SSL_GETPID(), ss->fd, data[0]));
    DUMP_MSG(29, (ss, data, ss->gs.recordLen));

    switch (data[0]) {
    case SSL_MT_CLIENT_FINISHED:
      if (ss->sec.ci.elements & CIS_HAVE_FINISHED) {
          SSL_DBG(("%d: SSL[%d]: dup client-finished message",
                 SSL_GETPID(), ss->fd));
          goto bad_peer;
      }

      /* See if nonce matches */
      len = ss->gs.recordLen - 1;
      cid = data + 1;
      if ((len != sizeof(ss->sec.ci.connectionID)) ||
          (PORT_Memcmp(ss->sec.ci.connectionID, cid, len) != 0)) {
          SSL_DBG(("%d: SSL[%d]: bad connection-id", SSL_GETPID(), ss->fd));
          PRINT_BUF(5, (ss, "sent connection-id",
                    ss->sec.ci.connectionID, 
                    sizeof(ss->sec.ci.connectionID)));
          PRINT_BUF(5, (ss, "rcvd connection-id", cid, len));
          goto bad_peer;
      }

      SSL_TRC(5, ("%d: SSL[%d]: got client finished, waiting for 0x%d",
                SSL_GETPID(), ss->fd, 
                ss->sec.ci.requiredElements ^ ss->sec.ci.elements));
      ss->sec.ci.elements |= CIS_HAVE_FINISHED;
      break;

    case SSL_MT_SERVER_FINISHED:
      if (ss->sec.ci.elements & CIS_HAVE_FINISHED) {
          SSL_DBG(("%d: SSL[%d]: dup server-finished message",
                 SSL_GETPID(), ss->fd));
          goto bad_peer;
      }

      if (ss->gs.recordLen - 1 != SSL2_SESSIONID_BYTES) {
          SSL_DBG(("%d: SSL[%d]: bad server-finished message, len=%d",
                 SSL_GETPID(), ss->fd, ss->gs.recordLen));
          goto bad_peer;
      }
      ssl2_ClientRegSessionID(ss, data+1);
      SSL_TRC(5, ("%d: SSL[%d]: got server finished, waiting for 0x%d",
                SSL_GETPID(), ss->fd, 
                ss->sec.ci.requiredElements ^ ss->sec.ci.elements));
      ss->sec.ci.elements |= CIS_HAVE_FINISHED;
      break;

    case SSL_MT_REQUEST_CERTIFICATE:
      len = ss->gs.recordLen - 2;
      if ((len < SSL_MIN_CHALLENGE_BYTES) ||
          (len > SSL_MAX_CHALLENGE_BYTES)) {
          /* Bad challenge */
          SSL_DBG(("%d: SSL[%d]: bad cert request message: code len=%d",
                 SSL_GETPID(), ss->fd, len));
          goto bad_peer;
      }
      
      /* save auth request info */
      ss->sec.ci.authType           = data[1];
      ss->sec.ci.serverChallengeLen = len;
      PORT_Memcpy(ss->sec.ci.serverChallenge, data + 2, len);
      
      rv = ssl2_HandleRequestCertificate(ss);
      if (rv == SECWouldBlock) {
          SSL_TRC(3, ("%d: SSL[%d]: async cert request",
                  SSL_GETPID(), ss->fd));
          /* someone is handling this asynchronously */
          ssl_ReleaseRecvBufLock(ss);
          return SECWouldBlock;
      }
      if (rv) {
          SET_ERROR_CODE
          goto loser;
      }
      break;

    case SSL_MT_CLIENT_CERTIFICATE:
      if (!ss->authCertificate) {
          /* Server asked for authentication and can't handle it */
          PORT_SetError(SSL_ERROR_BAD_SERVER);
          goto loser;
      }
      if (ss->gs.recordLen < SSL_HL_CLIENT_CERTIFICATE_HBYTES) {
          SET_ERROR_CODE
          goto loser;
      }
      certType    = data[1];
      certLen     = (data[2] << 8) | data[3];
      responseLen = (data[4] << 8) | data[5];
      if (certType != SSL_CT_X509_CERTIFICATE) {
          PORT_SetError(SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE);
          goto loser;
      }
      if (certLen + responseLen + SSL_HL_CLIENT_CERTIFICATE_HBYTES 
          > ss->gs.recordLen) {
          /* prevent overflow crash. */
          rv = SECFailure;
      } else
      rv = ssl2_HandleClientCertificate(ss, data[1],
            data + SSL_HL_CLIENT_CERTIFICATE_HBYTES,
            certLen,
            data + SSL_HL_CLIENT_CERTIFICATE_HBYTES + certLen,
            responseLen);
      if (rv) {
          rv2 = ssl2_SendErrorMessage(ss, SSL_PE_BAD_CERTIFICATE);
          SET_ERROR_CODE
          goto loser;
      }
      ss->sec.ci.elements |= CIS_HAVE_CERTIFICATE;
      break;

    case SSL_MT_ERROR:
      rv = (data[1] << 8) | data[2];
      SSL_TRC(2, ("%d: SSL[%d]: got error message, error=0x%x",
                SSL_GETPID(), ss->fd, rv));

      /* Convert protocol error number into API error number */
      switch (rv) {
        case SSL_PE_NO_CYPHERS:
          rv = SSL_ERROR_NO_CYPHER_OVERLAP;
          break;
        case SSL_PE_NO_CERTIFICATE:
          rv = SSL_ERROR_NO_CERTIFICATE;
          break;
        case SSL_PE_BAD_CERTIFICATE:
          rv = SSL_ERROR_BAD_CERTIFICATE;
          break;
        case SSL_PE_UNSUPPORTED_CERTIFICATE_TYPE:
          rv = SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE;
          break;
        default:
          goto bad_peer;
      }
      /* XXX make certificate-request optionally fail... */
      PORT_SetError(rv);
      goto loser;

    default:
      SSL_DBG(("%d: SSL[%d]: unknown message %d",
             SSL_GETPID(), ss->fd, data[0]));
      goto loser;
    }

    SSL_TRC(3, ("%d: SSL[%d]: handled %d message, required=0x%x got=0x%x",
            SSL_GETPID(), ss->fd, data[0],
            ss->sec.ci.requiredElements, ss->sec.ci.elements));

    rv = ssl2_TryToFinish(ss);
    if (rv != SECSuccess) 
      goto loser;

    ss->gs.recordLen = 0;
    ssl_ReleaseRecvBufLock(ss);

    if (ss->handshake == 0) {
      return SECSuccess;
    }

    ss->handshake     = ssl_GatherRecord1stHandshake;
    ss->nextHandshake = ssl2_HandleMessage;
    return ssl2_TriggerNextMessage(ss);

  bad_peer:
    PORT_SetError(ss->sec.isServer ? SSL_ERROR_BAD_CLIENT : SSL_ERROR_BAD_SERVER);
    /* FALL THROUGH */

  loser:
    ssl_ReleaseRecvBufLock(ss);
    return SECFailure;
}

/************************************************************************/

/* Called from ssl_Do1stHandshake, after ssl2_HandleServerHelloMessage or 
** ssl2_RestartHandshakeAfterServerCert.
*/
static SECStatus
ssl2_HandleVerifyMessage(sslSocket *ss)
{
    PRUint8 *        data;
    SECStatus        rv;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
    ssl_GetRecvBufLock(ss);

    data = ss->gs.buf.buf + ss->gs.recordOffset;
    DUMP_MSG(29, (ss, data, ss->gs.recordLen));
    if ((ss->gs.recordLen != 1 + SSL_CHALLENGE_BYTES) ||
      (data[0] != SSL_MT_SERVER_VERIFY) ||
      NSS_SecureMemcmp(data+1, ss->sec.ci.clientChallenge,
                       SSL_CHALLENGE_BYTES)) {
      /* Bad server */
      PORT_SetError(SSL_ERROR_BAD_SERVER);
      goto loser;
    }
    ss->sec.ci.elements |= CIS_HAVE_VERIFY;

    SSL_TRC(5, ("%d: SSL[%d]: got server-verify, required=0x%d got=0x%x",
            SSL_GETPID(), ss->fd, ss->sec.ci.requiredElements,
            ss->sec.ci.elements));

    rv = ssl2_TryToFinish(ss);
    if (rv) 
      goto loser;

    ss->gs.recordLen = 0;
    ssl_ReleaseRecvBufLock(ss);

    if (ss->handshake == 0) {
      return SECSuccess;
    }
    ss->handshake         = ssl_GatherRecord1stHandshake;
    ss->nextHandshake     = ssl2_HandleMessage;
    return SECSuccess;


  loser:
    ssl_ReleaseRecvBufLock(ss);
    return SECFailure;
}

/* Not static because ssl2_GatherData() tests ss->nextHandshake for this value.
 * ICK! 
 * Called from ssl_Do1stHandshake after ssl2_BeginClientHandshake()
 */
SECStatus
ssl2_HandleServerHelloMessage(sslSocket *ss)
{
    sslSessionID *   sid;
    PRUint8 *        cert;
    PRUint8 *        cs;
    PRUint8 *        data;
    SECStatus        rv; 
    int              needed, sidHit, certLen, csLen, cidLen, certType, err;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    if (!ss->opt.enableSSL2) {
      PORT_SetError(SSL_ERROR_SSL2_DISABLED);
      return SECFailure;
    }

    ssl_GetRecvBufLock(ss);

    PORT_Assert(ss->sec.ci.sid != 0);
    sid = ss->sec.ci.sid;

    data = ss->gs.buf.buf + ss->gs.recordOffset;
    DUMP_MSG(29, (ss, data, ss->gs.recordLen));

    /* Make sure first message has some data and is the server hello message */
    if ((ss->gs.recordLen < SSL_HL_SERVER_HELLO_HBYTES)
      || (data[0] != SSL_MT_SERVER_HELLO)) {
      if ((data[0] == SSL_MT_ERROR) && (ss->gs.recordLen == 3)) {
          err = (data[1] << 8) | data[2];
          if (err == SSL_PE_NO_CYPHERS) {
            PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
            goto loser;
          }
      }
      goto bad_server;
    }

    sidHit      = data[1];
    certType    = data[2];
    ss->version = (data[3] << 8) | data[4];
    certLen     = (data[5] << 8) | data[6];
    csLen       = (data[7] << 8) | data[8];
    cidLen      = (data[9] << 8) | data[10];
    cert        = data + SSL_HL_SERVER_HELLO_HBYTES;
    cs          = cert + certLen;

    SSL_TRC(5,
          ("%d: SSL[%d]: server-hello, hit=%d vers=%x certLen=%d csLen=%d cidLen=%d",
           SSL_GETPID(), ss->fd, sidHit, ss->version, certLen,
           csLen, cidLen));
    if (ss->version != SSL_LIBRARY_VERSION_2) {
        if (ss->version < SSL_LIBRARY_VERSION_2) {
        SSL_TRC(3, ("%d: SSL[%d]: demoting self (%x) to server version (%x)",
                  SSL_GETPID(), ss->fd, SSL_LIBRARY_VERSION_2,
                  ss->version));
      } else {
        SSL_TRC(1, ("%d: SSL[%d]: server version is %x (we are %x)",
                SSL_GETPID(), ss->fd, ss->version, SSL_LIBRARY_VERSION_2));
        /* server claims to be newer but does not follow protocol */
        PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION);
        goto loser;
      }
    }

    if ((SSL_HL_SERVER_HELLO_HBYTES + certLen + csLen + cidLen 
                                                  > ss->gs.recordLen)
      || (csLen % 3) != 0   
      /* || cidLen < SSL_CONNECTIONID_BYTES || cidLen > 32  */
      ) {
      goto bad_server;
    }

    /* Save connection-id.
    ** This code only saves the first 16 byte of the connectionID.
    ** If the connectionID is shorter than 16 bytes, it is zero-padded.
    */
    if (cidLen < sizeof ss->sec.ci.connectionID)
      memset(ss->sec.ci.connectionID, 0, sizeof ss->sec.ci.connectionID);
    cidLen = PR_MIN(cidLen, sizeof ss->sec.ci.connectionID);
    PORT_Memcpy(ss->sec.ci.connectionID, cs + csLen, cidLen);

    /* See if session-id hit */
    needed = CIS_HAVE_MASTER_KEY | CIS_HAVE_FINISHED | CIS_HAVE_VERIFY;
    if (sidHit) {
      if (certLen || csLen) {
          /* Uh oh - bogus server */
          SSL_DBG(("%d: SSL[%d]: client, huh? hit=%d certLen=%d csLen=%d",
                 SSL_GETPID(), ss->fd, sidHit, certLen, csLen));
          goto bad_server;
      }

      /* Total winner. */
      SSL_TRC(1, ("%d: SSL[%d]: client, using nonce for peer=0x%08x "
                "port=0x%04x",
                SSL_GETPID(), ss->fd, ss->sec.ci.peer, ss->sec.ci.port));
      ss->sec.peerCert = CERT_DupCertificate(sid->peerCert);
        ss->sec.authAlgorithm = sid->authAlgorithm;
      ss->sec.authKeyBits   = sid->authKeyBits;
      ss->sec.keaType       = sid->keaType;
      ss->sec.keaKeyBits    = sid->keaKeyBits;
      rv = ssl2_CreateSessionCypher(ss, sid, PR_TRUE);
      if (rv != SECSuccess) {
          goto loser;
      }
    } else {
      if (certType != SSL_CT_X509_CERTIFICATE) {
          PORT_SetError(SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE);
          goto loser;
      }
      if (csLen == 0) {
          PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
          SSL_DBG(("%d: SSL[%d]: no cipher overlap",
                 SSL_GETPID(), ss->fd));
          goto loser;
      }
      if (certLen == 0) {
          SSL_DBG(("%d: SSL[%d]: client, huh? certLen=%d csLen=%d",
                 SSL_GETPID(), ss->fd, certLen, csLen));
          goto bad_server;
      }

      if (sid->cached != never_cached) {
          /* Forget our session-id - server didn't like it */
          SSL_TRC(7, ("%d: SSL[%d]: server forgot me, uncaching session-id",
                  SSL_GETPID(), ss->fd));
          (*ss->sec.uncache)(sid);
          ssl_FreeSID(sid);
          ss->sec.ci.sid = sid = (sslSessionID*) PORT_ZAlloc(sizeof(sslSessionID));
          if (!sid) {
            goto loser;
          }
          sid->references = 1;
          sid->addr = ss->sec.ci.peer;
          sid->port = ss->sec.ci.port;
      }

      /* decode the server's certificate */
      rv = ssl2_ClientHandleServerCert(ss, cert, certLen);
      if (rv != SECSuccess) {
          if (PORT_GetError() == SSL_ERROR_BAD_CERTIFICATE) {
            (void) ssl2_SendErrorMessage(ss, SSL_PE_BAD_CERTIFICATE);
          }
          goto loser;
      }

      /* Setup new session cipher */
      rv = ssl2_ClientSetupSessionCypher(ss, cs, csLen);
      if (rv != SECSuccess) {
          if (PORT_GetError() == SSL_ERROR_BAD_CERTIFICATE) {
            (void) ssl2_SendErrorMessage(ss, SSL_PE_BAD_CERTIFICATE);
          }
          goto loser;
      }
    }

    /* Build up final list of required elements */
    ss->sec.ci.elements         = CIS_HAVE_MASTER_KEY;
    ss->sec.ci.requiredElements = needed;

  if (!sidHit) {
    /* verify the server's certificate. if sidHit, don't check signatures */
    rv = (* ss->authCertificate)(ss->authCertificateArg, ss->fd, 
                         (PRBool)(!sidHit), PR_FALSE);
    if (rv) {
      if (ss->handleBadCert) {
          rv = (*ss->handleBadCert)(ss->badCertArg, ss->fd);
          if ( rv ) {
            if ( rv == SECWouldBlock ) {
                /* someone will handle this connection asynchronously*/

                SSL_DBG(("%d: SSL[%d]: go to async cert handler",
                       SSL_GETPID(), ss->fd));
                ssl_ReleaseRecvBufLock(ss);
                ssl_SetAlwaysBlock(ss);
                return SECWouldBlock;
            }
            /* cert is bad */
            SSL_DBG(("%d: SSL[%d]: server certificate is no good: error=%d",
                   SSL_GETPID(), ss->fd, PORT_GetError()));
            goto loser;

          }
          /* cert is good */
      } else {
          SSL_DBG(("%d: SSL[%d]: server certificate is no good: error=%d",
                 SSL_GETPID(), ss->fd, PORT_GetError()));
          goto loser;
      }
    }
  }
    /*
    ** At this point we have a completed session key and our session
    ** cipher is setup and ready to go. Switch to encrypted write routine
    ** as all future message data is to be encrypted.
    */
    ssl2_UseEncryptedSendFunc(ss);

    rv = ssl2_TryToFinish(ss);
    if (rv != SECSuccess) 
      goto loser;

    ss->gs.recordLen = 0;

    ssl_ReleaseRecvBufLock(ss);

    if (ss->handshake == 0) {
      return SECSuccess;
    }

    SSL_TRC(5, ("%d: SSL[%d]: got server-hello, required=0x%d got=0x%x",
            SSL_GETPID(), ss->fd, ss->sec.ci.requiredElements, 
            ss->sec.ci.elements));
    ss->handshake     = ssl_GatherRecord1stHandshake;
    ss->nextHandshake = ssl2_HandleVerifyMessage;
    return SECSuccess;

  bad_server:
    PORT_SetError(SSL_ERROR_BAD_SERVER);
    /* FALL THROUGH */

  loser:
    ssl_ReleaseRecvBufLock(ss);
    return SECFailure;
}

/* Sends out the initial client Hello message on the connection.
 * Acquires and releases the socket's xmitBufLock.
 */
SECStatus
ssl2_BeginClientHandshake(sslSocket *ss)
{
    sslSessionID      *sid;
    PRUint8           *msg;
    PRUint8           *cp;
    PRUint8           *localCipherSpecs = NULL;
    unsigned int      localCipherSize;
    unsigned int      i;
    int               sendLen, sidLen = 0;
    SECStatus         rv;
    TLSExtensionData  *xtnData;

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    ss->sec.isServer     = 0;
    ss->sec.sendSequence = 0;
    ss->sec.rcvSequence  = 0;
    ssl_ChooseSessionIDProcs(&ss->sec);

    if (!ss->cipherSpecs) {
      rv = ssl2_ConstructCipherSpecs(ss);
      if (rv != SECSuccess)
          goto loser;
    }

    /* count the SSL2 and SSL3 enabled ciphers.
     * if either is zero, clear the socket's enable for that protocol.
     */
    rv = ssl2_CheckConfigSanity(ss);
    if (rv != SECSuccess)
      goto loser;

    /* Get peer name of server */
    rv = ssl_GetPeerInfo(ss);
    if (rv < 0) {
#ifdef HPUX11
        /*
         * On some HP-UX B.11.00 systems, getpeername() occasionally
         * fails with ENOTCONN after a successful completion of
         * non-blocking connect.  I found that if we do a write()
         * and then retry getpeername(), it will work.
         */
        if (PR_GetError() == PR_NOT_CONNECTED_ERROR) {
            char dummy;
            (void) PR_Write(ss->fd->lower, &dummy, 0);
            rv = ssl_GetPeerInfo(ss);
            if (rv < 0) {
                goto loser;
            }
        }
#else
      goto loser;
#endif
    }

    SSL_TRC(3, ("%d: SSL[%d]: sending client-hello", SSL_GETPID(), ss->fd));

    /* Try to find server in our session-id cache */
    if (ss->opt.noCache) {
      sid = NULL;
    } else {
      sid = ssl_LookupSID(&ss->sec.ci.peer, ss->sec.ci.port, ss->peerID, 
                          ss->url);
    }
    while (sid) {  /* this isn't really a loop */
      /* if we're not doing this SID's protocol any more, drop it. */
      if (((sid->version  < SSL_LIBRARY_VERSION_3_0) && !ss->opt.enableSSL2) ||
          ((sid->version == SSL_LIBRARY_VERSION_3_0) && !ss->opt.enableSSL3) ||
          ((sid->version >  SSL_LIBRARY_VERSION_3_0) && !ss->opt.enableTLS)) {
          ss->sec.uncache(sid);
          ssl_FreeSID(sid);
          sid = NULL;
          break;
      }
      if (ss->opt.enableSSL2 && sid->version < SSL_LIBRARY_VERSION_3_0) {
          /* If the cipher in this sid is not enabled, drop it. */
          for (i = 0; i < ss->sizeCipherSpecs; i += 3) {
            if (ss->cipherSpecs[i] == sid->u.ssl2.cipherType)
                break;
          }
          if (i >= ss->sizeCipherSpecs) {
            ss->sec.uncache(sid);
            ssl_FreeSID(sid);
            sid = NULL;
            break;
          }
      }
      sidLen = sizeof(sid->u.ssl2.sessionID);
      PRINT_BUF(4, (ss, "client, found session-id:", sid->u.ssl2.sessionID,
                  sidLen));
      ss->version = sid->version;
      PORT_Assert(!ss->sec.localCert);
      if (ss->sec.localCert) {
          CERT_DestroyCertificate(ss->sec.localCert);
      }
      ss->sec.localCert     = CERT_DupCertificate(sid->localCert);
      break;  /* this isn't really a loop */
    } 
    if (!sid) {
      sidLen = 0;
      sid = (sslSessionID*) PORT_ZAlloc(sizeof(sslSessionID));
      if (!sid) {
          goto loser;
      }
      sid->references = 1;
      sid->cached     = never_cached;
      sid->addr       = ss->sec.ci.peer;
      sid->port       = ss->sec.ci.port;
      if (ss->peerID != NULL) {
          sid->peerID = PORT_Strdup(ss->peerID);
      }
      if (ss->url != NULL) {
          sid->urlSvrName = PORT_Strdup(ss->url);
      }
    }
    ss->sec.ci.sid = sid;

    PORT_Assert(sid != NULL);

    if ((sid->version >= SSL_LIBRARY_VERSION_3_0 || !ss->opt.v2CompatibleHello) &&
        (ss->opt.enableSSL3 || ss->opt.enableTLS)) {

      ss->gs.state      = GS_INIT;
      ss->handshake     = ssl_GatherRecord1stHandshake;

      /* ssl3_SendClientHello will override this if it succeeds. */
      ss->version       = SSL_LIBRARY_VERSION_3_0;

      ssl_GetXmitBufLock(ss);    /***************************************/
      ssl_GetSSL3HandshakeLock(ss);
      rv =  ssl3_SendClientHello(ss);
      ssl_ReleaseSSL3HandshakeLock(ss);
      ssl_ReleaseXmitBufLock(ss); /***************************************/

      return rv;
    }
#if defined(NSS_ENABLE_ECC) && !defined(NSS_ECC_MORE_THAN_SUITE_B)
    /* ensure we don't neogtiate ECC cipher suites with SSL2 hello */
    ssl3_DisableECCSuites(ss, NULL); /* disable all ECC suites */
    if (ss->cipherSpecs != NULL) {
      PORT_Free(ss->cipherSpecs);
      ss->cipherSpecs     = NULL;
      ss->sizeCipherSpecs = 0;
    }
#endif

    if (!ss->cipherSpecs) {
        rv = ssl2_ConstructCipherSpecs(ss);
      if (rv < 0) {
          return rv;
      }
    }
    localCipherSpecs = ss->cipherSpecs;
    localCipherSize  = ss->sizeCipherSpecs;

    /* Add 3 for SCSV */
    sendLen = SSL_HL_CLIENT_HELLO_HBYTES + localCipherSize + 3 + sidLen +
      SSL_CHALLENGE_BYTES;

    /* Generate challenge bytes for server */
    PK11_GenerateRandom(ss->sec.ci.clientChallenge, SSL_CHALLENGE_BYTES);

    ssl_GetXmitBufLock(ss);    /***************************************/

    rv = ssl2_GetSendBuffer(ss, sendLen);
    if (rv) 
      goto unlock_loser;

    /* Construct client-hello message */
    cp = msg = ss->sec.ci.sendBuf.buf;
    msg[0] = SSL_MT_CLIENT_HELLO;
    if ( ss->opt.enableTLS ) {
      ss->clientHelloVersion = SSL_LIBRARY_VERSION_3_1_TLS;
    } else if ( ss->opt.enableSSL3 ) {
      ss->clientHelloVersion = SSL_LIBRARY_VERSION_3_0;
    } else {
      ss->clientHelloVersion = SSL_LIBRARY_VERSION_2;
    }
    
    msg[1] = MSB(ss->clientHelloVersion);
    msg[2] = LSB(ss->clientHelloVersion);
    /* Add 3 for SCSV */
    msg[3] = MSB(localCipherSize + 3);
    msg[4] = LSB(localCipherSize + 3);
    msg[5] = MSB(sidLen);
    msg[6] = LSB(sidLen);
    msg[7] = MSB(SSL_CHALLENGE_BYTES);
    msg[8] = LSB(SSL_CHALLENGE_BYTES);
    cp += SSL_HL_CLIENT_HELLO_HBYTES;
    PORT_Memcpy(cp, localCipherSpecs, localCipherSize);
    cp += localCipherSize;
    /*
     * Add SCSV.  SSL 2.0 cipher suites are listed before SSL 3.0 cipher
     * suites in localCipherSpecs for compatibility with SSL 2.0 servers.
     * Since SCSV looks like an SSL 3.0 cipher suite, we can't add it at
     * the beginning.
     */
    cp[0] = 0x00;
    cp[1] = 0x00;
    cp[2] = 0xff;
    cp += 3;
    if (sidLen) {
      PORT_Memcpy(cp, sid->u.ssl2.sessionID, sidLen);
      cp += sidLen;
    }
    PORT_Memcpy(cp, ss->sec.ci.clientChallenge, SSL_CHALLENGE_BYTES);

    /* Send it to the server */
    DUMP_MSG(29, (ss, msg, sendLen));
    ss->handshakeBegun = 1;
    rv = (*ss->sec.send)(ss, msg, sendLen, 0);

    ssl_ReleaseXmitBufLock(ss);    /***************************************/

    if (rv < 0) {
      goto loser;
    }

    rv = ssl3_StartHandshakeHash(ss, msg, sendLen);
    if (rv < 0) {
      goto loser;
    }

    /*
     * Since we sent the SCSV, pretend we sent empty RI extension.  We need
     * to record the extension has been advertised after ssl3_InitState has
     * been called, which ssl3_StartHandshakeHash took care for us above.
     */
    xtnData = &ss->xtnData;
    xtnData->advertised[xtnData->numAdvertised++] = ssl_renegotiation_info_xtn;

    /* Setup to receive servers hello message */
    ssl_GetRecvBufLock(ss);
    ss->gs.recordLen = 0;
    ssl_ReleaseRecvBufLock(ss);

    ss->handshake     = ssl_GatherRecord1stHandshake;
    ss->nextHandshake = ssl2_HandleServerHelloMessage;
    return SECSuccess;

unlock_loser:
    ssl_ReleaseXmitBufLock(ss);
loser:
    return SECFailure;
}

/************************************************************************/

/* Handle the CLIENT-MASTER-KEY message. 
** Acquires and releases RecvBufLock.
** Called from ssl2_HandleClientHelloMessage(). 
*/
static SECStatus
ssl2_HandleClientSessionKeyMessage(sslSocket *ss)
{
    PRUint8 *        data;
    unsigned int     caLen;
    unsigned int     ckLen;
    unsigned int     ekLen;
    unsigned int     keyBits;
    int              cipher;
    SECStatus        rv;


    ssl_GetRecvBufLock(ss);

    data = ss->gs.buf.buf + ss->gs.recordOffset;
    DUMP_MSG(29, (ss, data, ss->gs.recordLen));

    if ((ss->gs.recordLen < SSL_HL_CLIENT_MASTER_KEY_HBYTES)
      || (data[0] != SSL_MT_CLIENT_MASTER_KEY)) {
      goto bad_client;
    }
    cipher  = data[1];
    keyBits = (data[2] << 8) | data[3];
    ckLen   = (data[4] << 8) | data[5];
    ekLen   = (data[6] << 8) | data[7];
    caLen   = (data[8] << 8) | data[9];

    SSL_TRC(5, ("%d: SSL[%d]: session-key, cipher=%d keyBits=%d ckLen=%d ekLen=%d caLen=%d",
            SSL_GETPID(), ss->fd, cipher, keyBits, ckLen, ekLen, caLen));

    if (ss->gs.recordLen < 
          SSL_HL_CLIENT_MASTER_KEY_HBYTES + ckLen + ekLen + caLen) {
      SSL_DBG(("%d: SSL[%d]: protocol size mismatch dataLen=%d",
             SSL_GETPID(), ss->fd, ss->gs.recordLen));
      goto bad_client;
    }

    /* Use info from client to setup session key */
    rv = ssl2_ServerSetupSessionCypher(ss, cipher, keyBits,
            data + SSL_HL_CLIENT_MASTER_KEY_HBYTES,                 ckLen,
            data + SSL_HL_CLIENT_MASTER_KEY_HBYTES + ckLen,         ekLen,
            data + SSL_HL_CLIENT_MASTER_KEY_HBYTES + ckLen + ekLen, caLen);
    ss->gs.recordLen = 0;     /* we're done with this record. */

    ssl_ReleaseRecvBufLock(ss);

    if (rv != SECSuccess) {
      goto loser;
    }
    ss->sec.ci.elements |= CIS_HAVE_MASTER_KEY;
    ssl2_UseEncryptedSendFunc(ss);

    /* Send server verify message now that keys are established */
    rv = ssl2_SendServerVerifyMessage(ss);
    if (rv != SECSuccess) 
      goto loser;

    rv = ssl2_TryToFinish(ss);
    if (rv != SECSuccess) 
      goto loser;
    if (ss->handshake == 0) {
      return SECSuccess;
    }

    SSL_TRC(5, ("%d: SSL[%d]: server: waiting for elements=0x%d",
            SSL_GETPID(), ss->fd, 
            ss->sec.ci.requiredElements ^ ss->sec.ci.elements));
    ss->handshake         = ssl_GatherRecord1stHandshake;
    ss->nextHandshake     = ssl2_HandleMessage;

    return ssl2_TriggerNextMessage(ss);

bad_client:
    ssl_ReleaseRecvBufLock(ss);
    PORT_SetError(SSL_ERROR_BAD_CLIENT);
    /* FALLTHROUGH */

loser:
    return SECFailure;
}

/*
 * attempt to restart the handshake after asynchronously handling
 * a request for the client's certificate.
 *
 * inputs:  
 *    cert  Client cert chosen by application.
 *    key   Private key associated with cert.  
 *
 * XXX: need to make ssl2 and ssl3 versions of this function agree on whether
 *    they take the reference, or bump the ref count!
 *
 * Return value: XXX
 *
 * Caller holds 1stHandshakeLock.
 */
int
ssl2_RestartHandshakeAfterCertReq(sslSocket *          ss,
                          CERTCertificate *    cert, 
                          SECKEYPrivateKey *   key)
{
    int              ret;
    SECStatus        rv          = SECSuccess;
    SECItem          response;

    if (ss->version >= SSL_LIBRARY_VERSION_3_0) 
      return SECFailure;

    response.data = NULL;

    /* generate error if no cert or key */
    if ( ( cert == NULL ) || ( key == NULL ) ) {
      goto no_cert;
    }
    
    /* generate signed response to the challenge */
    rv = ssl2_SignResponse(ss, key, &response);
    if ( rv != SECSuccess ) {
      goto no_cert;
    }
    
    /* Send response message */
    ret = ssl2_SendCertificateResponseMessage(ss, &cert->derCert, &response);
    if (ret) {
      goto no_cert;
    }

    /* try to finish the handshake */
    ret = ssl2_TryToFinish(ss);
    if (ret) {
      goto loser;
    }
    
    /* done with handshake */
    if (ss->handshake == 0) {
      ret = SECSuccess;
      goto done;
    }

    /* continue handshake */
    ssl_GetRecvBufLock(ss);
    ss->gs.recordLen = 0;
    ssl_ReleaseRecvBufLock(ss);

    ss->handshake     = ssl_GatherRecord1stHandshake;
    ss->nextHandshake = ssl2_HandleMessage;
    ret = ssl2_TriggerNextMessage(ss);
    goto done;
    
no_cert:
    /* no cert - send error */
    ret = ssl2_SendErrorMessage(ss, SSL_PE_NO_CERTIFICATE);
    goto done;
    
loser:
    ret = SECFailure;
done:
    /* free allocated data */
    if ( response.data ) {
      PORT_Free(response.data);
    }
    
    return ret;
}


/* restart an SSL connection that we stopped to run certificate dialogs 
** XXX      Need to document here how an application marks a cert to show that
**    the application has accepted it (overridden CERT_VerifyCert).
 *
 * Return value: XXX
 *
 * Caller holds 1stHandshakeLock.
*/
int
ssl2_RestartHandshakeAfterServerCert(sslSocket *ss)
{
    int rv  = SECSuccess;

    if (ss->version >= SSL_LIBRARY_VERSION_3_0) 
      return SECFailure;

    /* SSL 2
    ** At this point we have a completed session key and our session
    ** cipher is setup and ready to go. Switch to encrypted write routine
    ** as all future message data is to be encrypted.
    */
    ssl2_UseEncryptedSendFunc(ss);

    rv = ssl2_TryToFinish(ss);
    if (rv == SECSuccess && ss->handshake != NULL) {  
      /* handshake is not yet finished. */

      SSL_TRC(5, ("%d: SSL[%d]: got server-hello, required=0x%d got=0x%x",
            SSL_GETPID(), ss->fd, ss->sec.ci.requiredElements,
            ss->sec.ci.elements));

      ssl_GetRecvBufLock(ss);
      ss->gs.recordLen = 0;   /* mark it all used up. */
      ssl_ReleaseRecvBufLock(ss);

      ss->handshake     = ssl_GatherRecord1stHandshake;
      ss->nextHandshake = ssl2_HandleVerifyMessage;
    }

    return rv;
}

/*
** Handle the initial hello message from the client
**
** not static because ssl2_GatherData() tests ss->nextHandshake for this value.
*/
SECStatus
ssl2_HandleClientHelloMessage(sslSocket *ss)
{
    sslSessionID    *sid;
    sslServerCerts * sc;
    CERTCertificate *serverCert;
    PRUint8         *msg;
    PRUint8         *data;
    PRUint8         *cs;
    PRUint8         *sd;
    PRUint8         *cert = NULL;
    PRUint8         *challenge;
    unsigned int    challengeLen;
    SECStatus       rv; 
    int             csLen;
    int             sendLen;
    int             sdLen;
    int             certLen;
    int             pid;
    int             sent;
    int             gotXmitBufLock = 0;
#if defined(SOLARIS) && defined(i386)
    volatile PRUint8 hit;
#else
    int             hit;
#endif
    PRUint8         csImpl[sizeof implementedCipherSuites];

    PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );

    sc = ss->serverCerts + kt_rsa;
    serverCert = sc->serverCert;

    ssl_GetRecvBufLock(ss);


    data = ss->gs.buf.buf + ss->gs.recordOffset;
    DUMP_MSG(29, (ss, data, ss->gs.recordLen));

    /* Make sure first message has some data and is the client hello message */
    if ((ss->gs.recordLen < SSL_HL_CLIENT_HELLO_HBYTES)
      || (data[0] != SSL_MT_CLIENT_HELLO)) {
      goto bad_client;
    }

    /* Get peer name of client */
    rv = ssl_GetPeerInfo(ss);
    if (rv != SECSuccess) {
      goto loser;
    }

    /* Examine version information */
    /*
     * See if this might be a V2 client hello asking to use the V3 protocol
     */
    if ((data[0] == SSL_MT_CLIENT_HELLO) && 
        (data[1] >= MSB(SSL_LIBRARY_VERSION_3_0)) && 
      (ss->opt.enableSSL3 || ss->opt.enableTLS)) {
      rv = ssl3_HandleV2ClientHello(ss, data, ss->gs.recordLen);
      if (rv != SECFailure) { /* Success */
          ss->handshake             = NULL;
          ss->nextHandshake         = ssl_GatherRecord1stHandshake;
          ss->securityHandshake     = NULL;
          ss->gs.state              = GS_INIT;

          /* ssl3_HandleV3ClientHello has set ss->version,
          ** and has gotten us a brand new sid.  
          */
          ss->sec.ci.sid->version  = ss->version;
      }
      ssl_ReleaseRecvBufLock(ss);
      return rv;
    }
    /* Previously, there was a test here to see if SSL2 was enabled.
    ** If not, an error code was set, and SECFailure was returned,
    ** without sending any error code to the other end of the connection.
    ** That test has been removed.  If SSL2 has been disabled, there
    ** should be no SSL2 ciphers enabled, and consequently, the code
    ** below should send the ssl2 error message SSL_PE_NO_CYPHERS.
    ** We now believe this is the correct thing to do, even when SSL2
    ** has been explicitly disabled by the application.
    */

    /* Extract info from message */
    ss->version = (data[1] << 8) | data[2];

    /* If some client thinks ssl v2 is 2.0 instead of 0.2, we'll allow it.  */
    if (ss->version >= SSL_LIBRARY_VERSION_3_0) {
      ss->version = SSL_LIBRARY_VERSION_2;
    }
    
    csLen        = (data[3] << 8) | data[4];
    sdLen        = (data[5] << 8) | data[6];
    challengeLen = (data[7] << 8) | data[8];
    cs           = data + SSL_HL_CLIENT_HELLO_HBYTES;
    sd           = cs + csLen;
    challenge    = sd + sdLen;
    PRINT_BUF(7, (ss, "server, client session-id value:", sd, sdLen));

    if (!csLen || (csLen % 3) != 0 || 
        (sdLen != 0 && sdLen != SSL2_SESSIONID_BYTES) ||
      challengeLen < SSL_MIN_CHALLENGE_BYTES || 
      challengeLen > SSL_MAX_CHALLENGE_BYTES ||
        (unsigned)ss->gs.recordLen != 
            SSL_HL_CLIENT_HELLO_HBYTES + csLen + sdLen + challengeLen) {
      SSL_DBG(("%d: SSL[%d]: bad client hello message, len=%d should=%d",
             SSL_GETPID(), ss->fd, ss->gs.recordLen,
             SSL_HL_CLIENT_HELLO_HBYTES+csLen+sdLen+challengeLen));
      goto bad_client;
    }

    SSL_TRC(3, ("%d: SSL[%d]: client version is %x",
            SSL_GETPID(), ss->fd, ss->version));
    if (ss->version != SSL_LIBRARY_VERSION_2) {
      if (ss->version > SSL_LIBRARY_VERSION_2) {
          /*
          ** Newer client than us. Things are ok because new clients
          ** are required to be backwards compatible with old servers.
          ** Change version number to our version number so that client
          ** knows whats up.
          */
          ss->version = SSL_LIBRARY_VERSION_2;
      } else {
          SSL_TRC(1, ("%d: SSL[%d]: client version is %x (we are %x)",
            SSL_GETPID(), ss->fd, ss->version, SSL_LIBRARY_VERSION_2));
          PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION);
          goto loser;
      }
    }

    /* Qualify cipher specs before returning them to client */
    csLen = ssl2_QualifyCypherSpecs(ss, cs, csLen);
    if (csLen == 0) {
      /* no overlap, send client our list of supported SSL v2 ciphers. */
        cs    = csImpl;
      csLen = sizeof implementedCipherSuites;
      PORT_Memcpy(cs, implementedCipherSuites, csLen);
      csLen = ssl2_QualifyCypherSpecs(ss, cs, csLen);
      if (csLen == 0) {
        /* We don't support any SSL v2 ciphers! */
        ssl2_SendErrorMessage(ss, SSL_PE_NO_CYPHERS);
        PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
        goto loser;
      }
      /* Since this handhsake is going to fail, don't cache it. */
      ss->opt.noCache = 1; 
    }

    /* Squirrel away the challenge for later */
    PORT_Memcpy(ss->sec.ci.clientChallenge, challenge, challengeLen);

    /* Examine message and see if session-id is good */
    ss->sec.ci.elements = 0;
    if (sdLen > 0 && !ss->opt.noCache) {
      SSL_TRC(7, ("%d: SSL[%d]: server, lookup client session-id for 0x%08x%08x%08x%08x",
                SSL_GETPID(), ss->fd, ss->sec.ci.peer.pr_s6_addr32[0],
                ss->sec.ci.peer.pr_s6_addr32[1], 
                ss->sec.ci.peer.pr_s6_addr32[2],
                ss->sec.ci.peer.pr_s6_addr32[3]));
      sid = (*ssl_sid_lookup)(&ss->sec.ci.peer, sd, sdLen, ss->dbHandle);
    } else {
      sid = NULL;
    }
    if (sid) {
      /* Got a good session-id. Short cut! */
      SSL_TRC(1, ("%d: SSL[%d]: server, using session-id for 0x%08x (age=%d)",
                SSL_GETPID(), ss->fd, ss->sec.ci.peer, 
                ssl_Time() - sid->creationTime));
      PRINT_BUF(1, (ss, "session-id value:", sd, sdLen));
      ss->sec.ci.sid = sid;
      ss->sec.ci.elements = CIS_HAVE_MASTER_KEY;
      hit = 1;
      certLen = 0;
      csLen = 0;

        ss->sec.authAlgorithm = sid->authAlgorithm;
      ss->sec.authKeyBits   = sid->authKeyBits;
      ss->sec.keaType       = sid->keaType;
      ss->sec.keaKeyBits    = sid->keaKeyBits;

      rv = ssl2_CreateSessionCypher(ss, sid, PR_FALSE);
      if (rv != SECSuccess) {
          goto loser;
      }
    } else {
      SECItem * derCert   = &serverCert->derCert;

      SSL_TRC(7, ("%d: SSL[%d]: server, lookup nonce missed",
                SSL_GETPID(), ss->fd));
      if (!serverCert) {
          SET_ERROR_CODE
          goto loser;
      }
      hit = 0;
      sid = (sslSessionID*) PORT_ZAlloc(sizeof(sslSessionID));
      if (!sid) {
          goto loser;
      }
      sid->references = 1;
      sid->addr = ss->sec.ci.peer;
      sid->port = ss->sec.ci.port;

      /* Invent a session-id */
      ss->sec.ci.sid = sid;
      PK11_GenerateRandom(sid->u.ssl2.sessionID+2, SSL2_SESSIONID_BYTES-2);

      pid = SSL_GETPID();
      sid->u.ssl2.sessionID[0] = MSB(pid);
      sid->u.ssl2.sessionID[1] = LSB(pid);
      cert    = derCert->data;
      certLen = derCert->len;

      /* pretend that server sids remember the local cert. */
      PORT_Assert(!sid->localCert);
      if (sid->localCert) {
          CERT_DestroyCertificate(sid->localCert);
      }
      sid->localCert     = CERT_DupCertificate(serverCert);

      ss->sec.authAlgorithm = ssl_sign_rsa;
      ss->sec.keaType       = ssl_kea_rsa;
      ss->sec.keaKeyBits    = \
      ss->sec.authKeyBits   = ss->serverCerts[kt_rsa].serverKeyBits;
    }

    /* server sids don't remember the local cert, so whether we found
    ** a sid or not, just "remember" we used the rsa server cert.
    */
    if (ss->sec.localCert) {
      CERT_DestroyCertificate(ss->sec.localCert);
    }
    ss->sec.localCert     = CERT_DupCertificate(serverCert);

    /* Build up final list of required elements */
    ss->sec.ci.requiredElements = CIS_HAVE_MASTER_KEY | CIS_HAVE_FINISHED;
    if (ss->opt.requestCertificate) {
      ss->sec.ci.requiredElements |= CIS_HAVE_CERTIFICATE;
    }
    ss->sec.ci.sentElements = 0;

    /* Send hello message back to client */
    sendLen = SSL_HL_SERVER_HELLO_HBYTES + certLen + csLen
          + SSL_CONNECTIONID_BYTES;

    ssl_GetXmitBufLock(ss); gotXmitBufLock = 1;
    rv = ssl2_GetSendBuffer(ss, sendLen);
    if (rv != SECSuccess) {
      goto loser;
    }

    SSL_TRC(3, ("%d: SSL[%d]: sending server-hello (%d)",
            SSL_GETPID(), ss->fd, sendLen));

    msg = ss->sec.ci.sendBuf.buf;
    msg[0] = SSL_MT_SERVER_HELLO;
    msg[1] = hit;
    msg[2] = SSL_CT_X509_CERTIFICATE;
    msg[3] = MSB(ss->version);
    msg[4] = LSB(ss->version);
    msg[5] = MSB(certLen);
    msg[6] = LSB(certLen);
    msg[7] = MSB(csLen);
    msg[8] = LSB(csLen);
    msg[9] = MSB(SSL_CONNECTIONID_BYTES);
    msg[10] = LSB(SSL_CONNECTIONID_BYTES);
    if (certLen) {
      PORT_Memcpy(msg+SSL_HL_SERVER_HELLO_HBYTES, cert, certLen);
    }
    if (csLen) {
      PORT_Memcpy(msg+SSL_HL_SERVER_HELLO_HBYTES+certLen, cs, csLen);
    }
    PORT_Memcpy(msg+SSL_HL_SERVER_HELLO_HBYTES+certLen+csLen, 
                ss->sec.ci.connectionID, SSL_CONNECTIONID_BYTES);

    DUMP_MSG(29, (ss, msg, sendLen));

    ss->handshakeBegun = 1;
    sent = (*ss->sec.send)(ss, msg, sendLen, 0);
    if (sent < 0) {
      goto loser;
    }
    ssl_ReleaseXmitBufLock(ss); gotXmitBufLock = 0;

    ss->gs.recordLen = 0;
    ss->handshake = ssl_GatherRecord1stHandshake;
    if (hit) {
      /* Old SID Session key is good. Go encrypted */
      ssl2_UseEncryptedSendFunc(ss);

      /* Send server verify message now that keys are established */
      rv = ssl2_SendServerVerifyMessage(ss);
      if (rv != SECSuccess) 
          goto loser;

      ss->nextHandshake = ssl2_HandleMessage;
      ssl_ReleaseRecvBufLock(ss);
      rv = ssl2_TriggerNextMessage(ss);
      return rv;
    }
    ss->nextHandshake = ssl2_HandleClientSessionKeyMessage;
    ssl_ReleaseRecvBufLock(ss);
    return SECSuccess;

  bad_client:
    PORT_SetError(SSL_ERROR_BAD_CLIENT);
    /* FALLTHROUGH */

  loser:
    if (gotXmitBufLock) {
      ssl_ReleaseXmitBufLock(ss); gotXmitBufLock = 0;
    }
    SSL_TRC(10, ("%d: SSL[%d]: server, wait for client-hello lossage",
             SSL_GETPID(), ss->fd));
    ssl_ReleaseRecvBufLock(ss);
    return SECFailure;
}

SECStatus
ssl2_BeginServerHandshake(sslSocket *ss)
{
    SECStatus        rv;
    sslServerCerts * rsaAuth = ss->serverCerts + kt_rsa;

    ss->sec.isServer = 1;
    ssl_ChooseSessionIDProcs(&ss->sec);
    ss->sec.sendSequence = 0;
    ss->sec.rcvSequence = 0;

    /* don't turn on SSL2 if we don't have an RSA key and cert */
    if (!rsaAuth->serverKeyPair || !rsaAuth->SERVERKEY || 
        !rsaAuth->serverCert) {
      ss->opt.enableSSL2 = PR_FALSE;
    }

    if (!ss->cipherSpecs) {
      rv = ssl2_ConstructCipherSpecs(ss);
      if (rv != SECSuccess)
          goto loser;
    }

    /* count the SSL2 and SSL3 enabled ciphers.
     * if either is zero, clear the socket's enable for that protocol.
     */
    rv = ssl2_CheckConfigSanity(ss);
    if (rv != SECSuccess)
      goto loser;

    /*
    ** Generate connection-id. Always do this, even if things fail
    ** immediately. This way the random number generator is always
    ** rolling around, every time we get a connection.
    */
    PK11_GenerateRandom(ss->sec.ci.connectionID, 
                        sizeof(ss->sec.ci.connectionID));

    ss->gs.recordLen = 0;
    ss->handshake     = ssl_GatherRecord1stHandshake;
    ss->nextHandshake = ssl2_HandleClientHelloMessage;
    return SECSuccess;

loser:
    return SECFailure;
}

/* This function doesn't really belong in this file.
** It's here to keep AIX compilers from optimizing it away, 
** and not including it in the DSO.
*/

#include "nss.h"
extern const char __nss_ssl_rcsid[];
extern const char __nss_ssl_sccsid[];

PRBool
NSSSSL_VersionCheck(const char *importedVersion)
{
    /*
     * This is the secret handshake algorithm.
     *
     * This release has a simple version compatibility
     * check algorithm.  This release is not backward
     * compatible with previous major releases.  It is
     * not compatible with future major, minor, or
     * patch releases.
     */
    volatile char c; /* force a reference that won't get optimized away */

    c = __nss_ssl_rcsid[0] + __nss_ssl_sccsid[0]; 
    return NSS_VersionCheck(importedVersion);
}

Generated by  Doxygen 1.6.0   Back to index