]> git.saurik.com Git - apple/security.git/blobdiff - SecureTransport/sslHandshake.cpp
Security-54.1.3.tar.gz
[apple/security.git] / SecureTransport / sslHandshake.cpp
diff --git a/SecureTransport/sslHandshake.cpp b/SecureTransport/sslHandshake.cpp
new file mode 100644 (file)
index 0000000..2ef0abe
--- /dev/null
@@ -0,0 +1,811 @@
+/*
+ * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
+ * 
+ * The contents of this file constitute Original Code as defined in and are
+ * subject to the Apple Public Source License Version 1.2 (the 'License').
+ * You may not use this file except in compliance with the License. Please obtain
+ * a copy of the License at http://www.apple.com/publicsource and read it before
+ * using this file.
+ * 
+ * This Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
+ * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
+ * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
+ * specific language governing rights and limitations under the License.
+ */
+
+
+/*
+       File:           sslHandshake.cpp
+
+       Contains:       SSL 3.0 handshake state machine. 
+
+       Written by:     Doug Mitchell
+
+       Copyright: (c) 1999 by Apple Computer, Inc., all rights reserved.
+
+*/
+
+#include "sslContext.h"
+#include "sslHandshake.h"
+#include "sslMemory.h"
+#include "sslAlertMessage.h"
+#include "sslSession.h"
+#include "sslUtils.h"
+#include "sslDebug.h"
+#include "appleCdsa.h"
+#include "sslDigests.h"
+
+#include <string.h>
+#include <assert.h>
+
+#define REQUEST_CERT_CORRECT        0
+
+static OSStatus SSLProcessHandshakeMessage(SSLHandshakeMsg message, SSLContext *ctx);
+
+OSStatus
+SSLProcessHandshakeRecord(SSLRecord rec, SSLContext *ctx)
+{   OSStatus        err;
+    sint32          remaining;
+    UInt8           *p;
+    SSLHandshakeMsg message;
+    SSLBuffer       messageData;
+    
+    if (ctx->fragmentedMessageCache.data != 0)
+    {   if ((err = SSLReallocBuffer(ctx->fragmentedMessageCache,
+                    ctx->fragmentedMessageCache.length + rec.contents.length,
+                    ctx)) != 0)
+        {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+            return err;
+        }
+        memcpy(ctx->fragmentedMessageCache.data + ctx->fragmentedMessageCache.length,
+            rec.contents.data, rec.contents.length);
+        remaining = ctx->fragmentedMessageCache.length;
+        p = ctx->fragmentedMessageCache.data;
+    }
+    else
+    {   remaining = rec.contents.length;
+        p = rec.contents.data;
+    }
+
+    while (remaining > 0)
+    {   if (remaining < 4)
+            break;  /* we must have at least a header */
+        
+        messageData.data = p;
+        message.type = (SSLHandshakeType)*p++;
+        message.contents.length = SSLDecodeInt(p, 3);
+        if (((int)(message.contents.length + 4)) > remaining)
+            break;
+        
+        p += 3;
+        message.contents.data = p;
+        p += message.contents.length;
+        messageData.length = 4 + message.contents.length;
+        assert(p == messageData.data + messageData.length);
+        
+        /* message fragmentation */
+        remaining -= messageData.length;
+        if ((err = SSLProcessHandshakeMessage(message, ctx)) != 0)
+            return err;
+        
+        if (message.type != SSL_HdskHelloRequest)
+        {   if ((err = SSLHashSHA1.update(ctx->shaState, messageData)) != 0 ||
+                (err = SSLHashMD5.update(ctx->md5State, messageData)) != 0)
+            {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                return err;
+            }
+        }
+        
+        if ((err = SSLAdvanceHandshake(message.type, ctx)) != 0)
+            return err;
+    }
+    
+    if (remaining > 0)      /* Fragmented handshake message */
+    {   /* If there isn't a cache, allocate one */
+        if (ctx->fragmentedMessageCache.data == 0)
+        {   if ((err = SSLAllocBuffer(ctx->fragmentedMessageCache, remaining, ctx)) != 0)
+            {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                return err;
+            }
+        }
+        if (p != ctx->fragmentedMessageCache.data)
+        {   memcpy(ctx->fragmentedMessageCache.data, p, remaining);
+            ctx->fragmentedMessageCache.length = remaining;
+        }
+    }
+    else if (ctx->fragmentedMessageCache.data != 0)
+    {   if ((err = SSLFreeBuffer(ctx->fragmentedMessageCache, ctx)) != 0)
+        {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+            return err;
+        }
+    }
+    
+    return noErr;
+}
+
+static OSStatus
+SSLProcessHandshakeMessage(SSLHandshakeMsg message, SSLContext *ctx)
+{   OSStatus      err;
+    
+    err = noErr;
+    SSLLogHdskMsg(message.type, 0);
+    switch (message.type)
+    {   case SSL_HdskHelloRequest:
+            if (ctx->protocolSide != SSL_ClientSide)
+                goto wrongMessage;
+            if (message.contents.length > 0)
+                err = errSSLProtocol;
+            break;
+        case SSL_HdskClientHello:
+            if (ctx->state != SSL_HdskStateServerUninit)
+                goto wrongMessage;
+            err = SSLProcessClientHello(message.contents, ctx);
+            break;
+        case SSL_HdskServerHello:
+            if (ctx->state != SSL_HdskStateServerHello &&
+                ctx->state != SSL_HdskStateServerHelloUnknownVersion)
+                goto wrongMessage;
+            err = SSLProcessServerHello(message.contents, ctx);
+            break;
+        case SSL_HdskCert:
+            if (ctx->state != SSL_HdskStateCert &&
+                ctx->state != SSL_HdskStateClientCert)
+                goto wrongMessage;
+            err = SSLProcessCertificate(message.contents, ctx);
+                       if(ctx->protocolSide == SSL_ServerSide) {
+                               if(err) {
+                                       ctx->clientCertState = kSSLClientCertRejected;
+                               }
+                               else if(ctx->peerCert != NULL) {
+                                       /* 
+                                        * This still might change if cert verify msg
+                                        * fails. Note we avoid going to state
+                                        * if we get en empty cert message which is
+                                        * otherwise valid.
+                                        */
+                                       ctx->clientCertState = kSSLClientCertSent;
+                               }
+                       }
+            break;
+        case SSL_HdskCertRequest:
+            if (((ctx->state != SSL_HdskStateHelloDone) && 
+                            (ctx->state != SSL_HdskStateKeyExchange))
+                 || ctx->certRequested)
+                goto wrongMessage;
+            err = SSLProcessCertificateRequest(message.contents, ctx);
+            break;
+        case SSL_HdskServerKeyExchange:
+                       /* 
+                * Since this message is optional, and completely at the
+                * server's discretion, we need to be able to handle this
+                * in one of two states...
+                */
+               switch(ctx->state) {
+                       case SSL_HdskStateKeyExchange:  /* explicitly waiting for this */
+                       case SSL_HdskStateHelloDone:
+                               break;
+                       default:
+                       goto wrongMessage;
+               }
+            err = SSLProcessServerKeyExchange(message.contents, ctx);
+            break;
+        case SSL_HdskServerHelloDone:
+            if (ctx->state != SSL_HdskStateHelloDone)
+                goto wrongMessage;
+            err = SSLProcessServerHelloDone(message.contents, ctx);
+            break;
+        case SSL_HdskCertVerify:
+            if (ctx->state != SSL_HdskStateClientCertVerify)
+                goto wrongMessage;
+            err = SSLProcessCertificateVerify(message.contents, ctx);
+                       assert(ctx->protocolSide == SSL_ServerSide);
+                       if(err) {
+                               ctx->clientCertState = kSSLClientCertRejected;
+                       }
+            break;
+        case SSL_HdskClientKeyExchange:
+            if (ctx->state != SSL_HdskStateClientKeyExchange)
+                goto wrongMessage;
+            err = SSLProcessKeyExchange(message.contents, ctx);
+            break;
+        case SSL_HdskFinished:
+            if (ctx->state != SSL_HdskStateFinished)
+                goto wrongMessage;
+            err = SSLProcessFinished(message.contents, ctx);
+            break;
+        default:
+            goto wrongMessage;
+            break;
+    }
+    
+    if (err)
+    {   if (err == errSSLProtocol)
+            SSLFatalSessionAlert(SSL_AlertIllegalParam, ctx);
+        else if (err == errSSLNegotiation)
+            SSLFatalSessionAlert(SSL_AlertHandshakeFail, ctx);
+        else
+            SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+    }
+    return err;
+    
+wrongMessage:
+    SSLFatalSessionAlert(SSL_AlertUnexpectedMsg, ctx);
+    return errSSLProtocol;
+}
+
+OSStatus
+SSLAdvanceHandshake(SSLHandshakeType processed, SSLContext *ctx)
+{   OSStatus        err;
+    SSLBuffer       sessionIdentifier;
+    
+    switch (processed)
+    {   case SSL_HdskHelloRequest:
+                       /* 
+                        * Reset the client auth state machine in case this is 
+                        * a renegotiation.
+                        */
+                       ctx->certRequested = 0;
+                       ctx->certSent = 0;
+                       ctx->certReceived = 0;
+                       ctx->x509Requested = 0;
+                       ctx->clientCertState = kSSLClientCertNone;
+            if ((err = SSLPrepareAndQueueMessage(SSLEncodeClientHello, ctx)) != 0)
+                return err;
+            SSLChangeHdskState(ctx, SSL_HdskStateServerHello);
+            break;
+        case SSL_HdskClientHello:
+            assert(ctx->protocolSide == SSL_ServerSide);
+            if (ctx->sessionID.data != 0)   
+                       /* If session ID != 0, client is trying to resume */
+            {   if (ctx->resumableSession.data != 0)
+                {   if ((err = SSLRetrieveSessionID(ctx->resumableSession, 
+                                                               &sessionIdentifier, ctx)) != 0)
+                        return err;
+                    if (sessionIdentifier.length == ctx->sessionID.length &&
+                        memcmp(sessionIdentifier.data, ctx->sessionID.data, 
+                                                                               ctx->sessionID.length) == 0)
+                    {   /* Everything matches; resume the session */
+                                               sslLogResumSessDebug("===RESUMING SSL3 server-side session");
+                        if ((err = SSLInstallSessionFromData(ctx->resumableSession,
+                                                               ctx)) != 0)
+                        {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                            return err;
+                        }
+                        if ((err = SSLPrepareAndQueueMessage(SSLEncodeServerHello, 
+                                                                       ctx)) != 0)
+                            return err;
+                        if ((err = SSLInitPendingCiphers(ctx)) != 0 ||
+                            (err = SSLFreeBuffer(sessionIdentifier, ctx)) != 0)
+                        {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                            return err;
+                        }
+                        if ((err = 
+                                                               SSLPrepareAndQueueMessage(SSLEncodeChangeCipherSpec,
+                                                                       ctx)) != 0)
+                            return err;
+                        /* Install new cipher spec on write side */
+                        if ((err = SSLDisposeCipherSuite(&ctx->writeCipher, 
+                                                               ctx)) != 0)
+                        {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                            return err;
+                        }
+                        ctx->writeCipher = ctx->writePending;
+                        ctx->writeCipher.ready = 0;     
+                                                               /* Can't send data until Finished is sent */
+                        memset(&ctx->writePending, 0, sizeof(CipherContext));       
+                                                               /* Zero out old data */
+                        if ((err = SSLPrepareAndQueueMessage(SSLEncodeFinishedMessage, 
+                                                               ctx)) != 0)
+                            return err;
+                        /* Finished has been sent; enable data t6ransfer on 
+                                                * write channel */
+                        ctx->writeCipher.ready = 1;
+                        SSLChangeHdskState(ctx, SSL_HdskStateChangeCipherSpec);
+                        break;
+                    }
+                                       else {
+                                               sslLogResumSessDebug(
+                                                       "===FAILED TO RESUME SSL3 server-side session");
+                                       }
+                    if ((err = SSLFreeBuffer(sessionIdentifier, ctx)) != 0 ||
+                        (err = SSLDeleteSessionData(ctx)) != 0)
+                    {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                        return err;
+                    }
+                }
+                if ((err = SSLFreeBuffer(ctx->sessionID, ctx)) != 0)
+                {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                    return err;
+                }
+            }
+            
+            /* 
+                        * If we get here, we're not resuming; generate a new session ID 
+                        * if we know our peer 
+                        */
+            if (ctx->peerID.data != 0)
+            {   /* Ignore errors; just treat as uncached session */
+                assert(ctx->sessionID.data == 0);
+                err = SSLAllocBuffer(ctx->sessionID, SSL_SESSION_ID_LEN, ctx);
+                if (err == 0)
+                {   
+                       if((err = sslRand(ctx, &ctx->sessionID)) != 0)
+                    {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                        return err;
+                    }
+                }
+            }
+            
+            if ((err = SSLPrepareAndQueueMessage(SSLEncodeServerHello, ctx)) != 0)
+                return err;
+            switch (ctx->selectedCipherSpec->keyExchangeMethod)
+            {   case SSL_NULL_auth:
+               #if             APPLE_DH
+                case SSL_DH_anon:
+                case SSL_DH_anon_EXPORT:
+                       if(ctx->clientAuth == kAlwaysAuthenticate) {
+                               /* app requires this; abort */
+                               SSLFatalSessionAlert(SSL_AlertHandshakeFail, ctx);
+                               return errSSLNegotiation;
+                       }
+                       ctx->tryClientAuth = false;
+                                       /* DH server side needs work */
+                    break;
+                #endif /* APPLE_DH */
+                default:        /* everything else */
+                    if ((err = SSLPrepareAndQueueMessage(SSLEncodeCertificate, 
+                                                       ctx)) != 0)
+                        return err;
+                    break;
+            }
+                       /*
+                        * At this point we decide whether to send a server key exchange
+                        * method. For Apple servers, I think we'll ALWAYS do this, because
+                        * of key usage restrictions (can't decrypt and sign with the same
+                        * private key), but conceptually in this code, we do it if 
+                        * enabled by the presence of encryptPrivKey. 
+                        */
+                       #if             SSL_SERVER_KEYEXCH_HACK 
+                               /*
+                                       * This is currently how we work with Netscape. It requires
+                                       * a CSP which can handle private keys which can both
+                                       * sign and decrypt. 
+                                       */
+                               if((ctx->selectedCipherSpec->keyExchangeMethod != SSL_RSA) &&
+                                       (ctx->encryptPrivKey != NULL)) {
+                                       err = SSLPrepareAndQueueMessage(SSLEncodeServerKeyExchange, ctx);
+                                       if(err) {
+                                               return err;
+                                       }
+                               }
+                       #else   /* !SSL_SERVER_KEYEXCH_HACK */
+                               /*
+                                       * This is, I believe the "right" way, but Netscape doesn't
+                                       * work this way.
+                                       */
+                               if (ctx->encryptPrivKey != NULL) {
+                                       err = SSLPrepareAndQueueMessage(SSLEncodeServerKeyExchange, ctx);
+                                       if(err) {
+                                               return err;
+                                       }
+                               }
+                       #endif  /* SSL_SERVER_KEYEXCH_HACK */
+
+            if (ctx->tryClientAuth)
+            {   if ((err = SSLPrepareAndQueueMessage(SSLEncodeCertificateRequest, 
+                                               ctx)) != 0)
+                    return err;
+                ctx->certRequested = 1;
+                               ctx->clientCertState = kSSLClientCertRequested;
+            }
+            if ((err = SSLPrepareAndQueueMessage(SSLEncodeServerHelloDone, ctx)) != 0)
+                return err;
+            if (ctx->certRequested) {
+                SSLChangeHdskState(ctx, SSL_HdskStateClientCert);
+            }
+            else {
+                SSLChangeHdskState(ctx, SSL_HdskStateClientKeyExchange);
+            }
+            break;
+        case SSL_HdskServerHello:
+            if (ctx->resumableSession.data != 0 && ctx->sessionID.data != 0)
+            {   if ((err = SSLRetrieveSessionID(ctx->resumableSession, 
+                                               &sessionIdentifier, ctx)) != 0)
+                {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                    return err;
+                }
+                if (sessionIdentifier.length == ctx->sessionID.length &&
+                    memcmp(sessionIdentifier.data, ctx->sessionID.data, 
+                                                       ctx->sessionID.length) == 0)
+                {   /* Everything matches; resume the session */
+                                       sslLogResumSessDebug("===RESUMING SSL3 client-side session");
+                    if ((err = SSLInstallSessionFromData(ctx->resumableSession,
+                                                       ctx)) != 0 ||
+                        (err = SSLInitPendingCiphers(ctx)) != 0 ||
+                        (err = SSLFreeBuffer(sessionIdentifier, ctx)) != 0)
+                    {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                        return err;
+                    }
+                    SSLChangeHdskState(ctx, SSL_HdskStateChangeCipherSpec);
+                    break;
+                }
+                               else {
+                                       sslLogResumSessDebug("===FAILED TO RESUME SSL3 client-side "
+                                                       "session");
+                               }
+                if ((err = SSLFreeBuffer(sessionIdentifier, ctx)) != 0)
+                {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                    return err;
+                }
+            }
+            switch (ctx->selectedCipherSpec->keyExchangeMethod)
+            {   
+               /* these require a key exchange message */
+               case SSL_NULL_auth:
+                case SSL_DH_anon:
+                case SSL_DH_anon_EXPORT:
+                    SSLChangeHdskState(ctx, SSL_HdskStateKeyExchange);
+                    break;
+                case SSL_RSA:
+                case SSL_DH_DSS:
+                case SSL_DH_DSS_EXPORT:
+                case SSL_DH_RSA:
+                case SSL_DH_RSA_EXPORT:
+                case SSL_RSA_EXPORT:
+                case SSL_DHE_DSS:
+                case SSL_DHE_DSS_EXPORT:
+                case SSL_DHE_RSA:
+                case SSL_DHE_RSA_EXPORT:
+                case SSL_Fortezza:
+                    SSLChangeHdskState(ctx, SSL_HdskStateCert);
+                    break;
+                default:
+                    assert("Unknown key exchange method");
+                    break;
+            }
+            break;
+        case SSL_HdskCert:
+            if (ctx->state == SSL_HdskStateCert)
+                switch (ctx->selectedCipherSpec->keyExchangeMethod)
+                {   case SSL_RSA:
+                       /*
+                        * I really think the two RSA cases should be
+                        * handled the same here - the server key exchange is
+                        * optional, and is up to the server.
+                        * Note this isn't the same as SSL_SERVER_KEYEXCH_HACK;
+                        * we're a client here.
+                        */                   
+                       case SSL_RSA_EXPORT:
+                    case SSL_DH_DSS:
+                    case SSL_DH_DSS_EXPORT:
+                    case SSL_DH_RSA:
+                    case SSL_DH_RSA_EXPORT:
+                        SSLChangeHdskState(ctx, SSL_HdskStateHelloDone);
+                        break;
+                    case SSL_DHE_DSS:
+                    case SSL_DHE_DSS_EXPORT:
+                    case SSL_DHE_RSA:
+                    case SSL_DHE_RSA_EXPORT:
+                    case SSL_Fortezza:
+                        SSLChangeHdskState(ctx, SSL_HdskStateKeyExchange);
+                        break;
+                    default:
+                        assert("Unknown or unexpected key exchange method");
+                        break;
+                }
+            else if (ctx->state == SSL_HdskStateClientCert)
+            {   SSLChangeHdskState(ctx, SSL_HdskStateClientKeyExchange);
+                if (ctx->peerCert != 0)
+                    ctx->certReceived = 1;
+            }
+            break;
+        case SSL_HdskCertRequest:   
+                       /* state stays in SSL_HdskStateHelloDone; distinction is in
+                        *  ctx->certRequested */
+            if (ctx->peerCert == 0)
+            {   SSLFatalSessionAlert(SSL_AlertHandshakeFail, ctx);
+                return errSSLProtocol;
+            }
+            ctx->certRequested = 1;
+                       ctx->clientCertState = kSSLClientCertRequested;
+            break;
+        case SSL_HdskServerKeyExchange:
+            SSLChangeHdskState(ctx, SSL_HdskStateHelloDone);
+            break;
+        case SSL_HdskServerHelloDone:
+            if (ctx->certRequested) {
+                               /* 
+                                * Server wants a client authentication cert - do 
+                                * we have one? 
+                                */
+                if (ctx->localCert != 0 && ctx->x509Requested) {
+                                       if ((err = SSLPrepareAndQueueMessage(SSLEncodeCertificate,
+                                                       ctx)) != 0) {
+                                               return err;
+                                       }
+                }
+                else {
+                                       /* response for no cert depends on protocol version */
+                                       if(ctx->negProtocolVersion == TLS_Version_1_0) {
+                                               /* TLS: send empty cert msg */
+                                               if ((err = SSLPrepareAndQueueMessage(SSLEncodeCertificate,
+                                                               ctx)) != 0) {
+                                                       return err;
+                                               }
+                                       }
+                                       else {
+                                               /* SSL3: "no cert" alert */
+                                               if ((err = SSLSendAlert(SSL_AlertLevelWarning, SSL_AlertNoCert,
+                                                               ctx)) != 0) {
+                                                       return err;
+                                               }
+                                       }
+                }      /* no cert to send */
+            }  /* server requested a cert */
+            if ((err = SSLPrepareAndQueueMessage(SSLEncodeKeyExchange, ctx)) != 0)
+                return err;
+                       assert(ctx->sslTslCalls != NULL);
+            if ((err = ctx->sslTslCalls->generateMasterSecret(ctx)) != 0 ||
+                (err = SSLInitPendingCiphers(ctx)) != 0)
+            {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                return err;
+            }
+                       memset(ctx->preMasterSecret.data, 0, ctx->preMasterSecret.length);
+            if ((err = SSLFreeBuffer(ctx->preMasterSecret, ctx)) != 0) {
+                return err;
+                       }
+            if (ctx->certSent) {
+                if ((err = SSLPrepareAndQueueMessage(SSLEncodeCertificateVerify, 
+                                               ctx)) != 0) {
+                    return err;
+                               }
+                       }
+            if ((err = SSLPrepareAndQueueMessage(SSLEncodeChangeCipherSpec, 
+                                       ctx)) != 0) {
+                return err;
+                       }
+            /* Install new cipher spec on write side */
+            if ((err = SSLDisposeCipherSuite(&ctx->writeCipher, ctx)) != 0)
+            {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                return err;
+            }
+            ctx->writeCipher = ctx->writePending;
+                       /* Can't send data until Finished is sent */
+            ctx->writeCipher.ready = 0;     
+                       
+                       /* Zero out old data */
+            memset(&ctx->writePending, 0, sizeof(CipherContext));    
+                       ctx->writePending.encrypting = 1;   
+            if ((err = SSLPrepareAndQueueMessage(SSLEncodeFinishedMessage, ctx)) != 0)
+                return err;
+            /* Finished has been sent; enable data transfer on write channel */
+            ctx->writeCipher.ready = 1;
+            SSLChangeHdskState(ctx, SSL_HdskStateChangeCipherSpec);
+            break;
+        case SSL_HdskCertVerify:
+            SSLChangeHdskState(ctx, SSL_HdskStateChangeCipherSpec);
+            break;
+        case SSL_HdskClientKeyExchange:
+                       assert(ctx->sslTslCalls != NULL);
+                       if ((err = ctx->sslTslCalls->generateMasterSecret(ctx)) != 0 ||
+                (err = SSLInitPendingCiphers(ctx)) != 0)
+            {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                return err;
+            }
+                       memset(ctx->preMasterSecret.data, 0, ctx->preMasterSecret.length);
+            if ((err = SSLFreeBuffer(ctx->preMasterSecret, ctx)) != 0)
+                return err;
+            if (ctx->certReceived) {
+                SSLChangeHdskState(ctx, SSL_HdskStateClientCertVerify);
+            }
+            else {
+                SSLChangeHdskState(ctx, SSL_HdskStateChangeCipherSpec);
+            }
+            break;
+        case SSL_HdskFinished:
+            /* Handshake is over; enable data transfer on read channel */
+            ctx->readCipher.ready = 1;
+            /* If writePending is set, we haven't yet sent a finished message; 
+                        * send it */
+            if (ctx->writePending.ready != 0)
+            {   if ((err = SSLPrepareAndQueueMessage(SSLEncodeChangeCipherSpec, 
+                                               ctx)) != 0)
+                    return err;
+                
+                /* Install new cipher spec on write side */
+                if ((err = SSLDisposeCipherSuite(&ctx->writeCipher, ctx)) != 0)
+                {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+                    return err;
+                }
+                ctx->writeCipher = ctx->writePending;
+                ctx->writeCipher.ready = 0;     
+                                               /* Can't send data until Finished is sent */
+                memset(&ctx->writePending, 0, sizeof(CipherContext));       
+                                               /* Zero out old data */
+                if ((err = SSLPrepareAndQueueMessage(SSLEncodeFinishedMessage, 
+                                                       ctx)) != 0)
+                    return err;
+                ctx->writeCipher.ready = 1;
+            }
+            if (ctx->protocolSide == SSL_ServerSide) {
+                SSLChangeHdskState(ctx, SSL2_HdskStateServerReady);
+            }
+            else {
+                SSLChangeHdskState(ctx, SSL2_HdskStateClientReady);
+            }
+            if (ctx->peerID.data != 0)
+                SSLAddSessionData(ctx);
+            break;
+        default:
+            assert("Unknown State");
+            break;
+    }
+    
+    return noErr;
+}
+
+OSStatus
+SSLPrepareAndQueueMessage(EncodeMessageFunc msgFunc, SSLContext *ctx)
+{   OSStatus        err;
+    SSLRecord       rec;
+    
+    if ((err = msgFunc(rec, ctx)) != 0)
+    {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+        goto fail;
+    }
+    
+    if (rec.contentType == SSL_RecordTypeHandshake)
+    {   if ((err = SSLHashSHA1.update(ctx->shaState, rec.contents)) != 0 ||
+            (err = SSLHashMD5.update(ctx->md5State, rec.contents)) != 0)
+        {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+            goto fail;
+        }
+        SSLLogHdskMsg((SSLHandshakeType)rec.contents.data[0], 1);
+    }
+    
+       assert(ctx->sslTslCalls != NULL);
+    if ((err = ctx->sslTslCalls->writeRecord(rec, ctx)) != 0)
+        goto fail;
+    
+    err = noErr;
+fail:
+    SSLFreeBuffer(rec.contents, ctx);
+    
+    return err;
+}
+
+OSStatus
+SSL3ReceiveSSL2ClientHello(SSLRecord rec, SSLContext *ctx)
+{   OSStatus      err;
+    
+    if ((err = SSLInitMessageHashes(ctx)) != 0)
+        return err;
+    
+    if ((err = SSLHashSHA1.update(ctx->shaState, rec.contents)) != 0 ||
+        (err = SSLHashMD5.update(ctx->md5State, rec.contents)) != 0)
+    {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+        return err;
+    }
+    
+    if ((err = SSLAdvanceHandshake(SSL_HdskClientHello, ctx)) != 0)
+        return err;
+    
+    return noErr;
+}
+
+/* log changes in handshake state */
+#ifndef        NDEBUG
+#include <stdio.h>
+
+char *hdskStateToStr(SSLHandshakeState state)
+{
+       static char badStr[100];
+       
+       switch(state) {
+               case SSL_HdskStateUninit:
+                       return "Uninit";        
+               case SSL_HdskStateServerUninit:
+                       return "ServerUninit";  
+               case SSL_HdskStateClientUninit:
+                       return "ClientUninit";  
+               case SSL_HdskStateGracefulClose:
+                       return "GracefulClose"; 
+               case SSL_HdskStateErrorClose:
+                       return "ErrorClose";            
+               case SSL_HdskStateNoNotifyClose:
+                       return "NoNotifyClose";
+               case SSL_HdskStateServerHello:
+                       return "ServerHello";   
+               case SSL_HdskStateServerHelloUnknownVersion:
+                       return "ServerHelloUnknownVersion";     
+               case SSL_HdskStateKeyExchange:
+                       return "KeyExchange";   
+               case SSL_HdskStateCert:
+                       return "Cert";  
+               case SSL_HdskStateHelloDone:
+                       return "HelloDone";     
+               case SSL_HdskStateClientCert:
+                       return "ClientCert";    
+               case SSL_HdskStateClientKeyExchange:
+                       return "ClientKeyExchange";     
+               case SSL_HdskStateClientCertVerify:
+                       return "ClientCertVerify";      
+               case SSL_HdskStateChangeCipherSpec:
+                       return "ChangeCipherSpec";      
+               case SSL_HdskStateFinished:
+                       return "Finished";      
+               case SSL2_HdskStateClientMasterKey:
+                       return "SSL2_ClientMasterKey";
+               case SSL2_HdskStateClientFinished:
+                       return "SSL2_ClientFinished";   
+               case SSL2_HdskStateServerHello:
+                       return "SSL2_ServerHello";      
+               case SSL2_HdskStateServerVerify:
+                       return "SSL2_ServerVerify";     
+               case SSL2_HdskStateServerFinished:
+                       return "SSL2_ServerFinished";   
+               case SSL2_HdskStateServerReady:
+                       return "SSL2_ServerReady";      
+               case SSL2_HdskStateClientReady:
+                       return "SSL2_ClientReady";
+               default:
+                       sprintf(badStr, "Unknown state (%d(d)", state);
+                       return badStr;
+       }
+}
+
+void SSLChangeHdskState(SSLContext *ctx, SSLHandshakeState newState)
+{
+       /* FIXME - this ifndef should not be necessary */
+       #ifndef NDEBUG
+       sslHdskStateDebug("...hdskState = %s", hdskStateToStr(newState));
+       #endif
+       ctx->state = newState;
+}
+
+
+/* log handshake messages */
+
+static char *hdskMsgToStr(SSLHandshakeType msg)
+{
+       static char badStr[100];
+       
+       switch(msg) {
+               case SSL_HdskHelloRequest:
+                       return "SSL_HdskHelloRequest";  
+               case SSL_HdskClientHello:
+                       return "SSL_HdskClientHello";   
+               case SSL_HdskServerHello:
+                       return "SSL_HdskServerHello";   
+               case SSL_HdskCert:
+                       return "SSL_HdskCert";  
+               case SSL_HdskServerKeyExchange:
+                       return "SSL_HdskServerKeyExchange";     
+               case SSL_HdskCertRequest:
+                       return "SSL_HdskCertRequest";   
+               case SSL_HdskServerHelloDone:
+                       return "SSL_HdskServerHelloDone";       
+               case SSL_HdskCertVerify:
+                       return "SSL_HdskCertVerify";    
+               case SSL_HdskClientKeyExchange:
+                       return "SSL_HdskClientKeyExchange";     
+               case SSL_HdskFinished:
+                       return "SSL_HdskFinished";      
+               case SSL_HdskNoCertAlert:
+                       return "SSL_HdskNoCertAlert";   
+               default:
+                       sprintf(badStr, "Unknown state (%d(d)", msg);
+                       return badStr;
+       }
+}
+
+void SSLLogHdskMsg(SSLHandshakeType msg, char sent)
+{
+       sslHdskMsgDebug("---%s handshake msg %s", 
+               hdskMsgToStr(msg), (sent ? "sent" : "recv"));
+}
+
+#endif /* NDEBUG */
+