]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_ssl/lib/sslHandshake.c
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_ssl / lib / sslHandshake.c
diff --git a/libsecurity_ssl/lib/sslHandshake.c b/libsecurity_ssl/lib/sslHandshake.c
new file mode 100644 (file)
index 0000000..9844c6f
--- /dev/null
@@ -0,0 +1,1362 @@
+/*
+ * Copyright (c) 1999-2001,2005-2012 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The 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.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+
+/*
+ * sslHandshake.c - SSL 3.0 handshake state machine.
+ */
+
+#include "sslContext.h"
+#include "sslHandshake.h"
+#include "sslMemory.h"
+#include "sslAlertMessage.h"
+#include "sslSession.h"
+#include "sslUtils.h"
+#include "sslDebug.h"
+#include "sslCrypto.h"
+
+#include "sslDigests.h"
+#include "cipherSpecs.h"
+
+#include <string.h>
+#include <assert.h>
+#include <inttypes.h>
+
+#define REQUEST_CERT_CORRECT        0
+
+#if __LP64__
+#define PRIstatus "d"
+#else
+#define PRIstatus "ld"
+#endif
+
+static OSStatus SSLProcessHandshakeMessage(SSLHandshakeMsg message, SSLContext *ctx);
+
+static OSStatus
+SSLUpdateHandshakeMacs(const SSLBuffer *messageData, SSLContext *ctx)
+{
+    OSStatus err = errSecParam;
+    bool do_md5 = false;
+    bool do_sha1 = false;
+    bool do_sha256 = false;
+    bool do_sha384 = false;
+
+    //TODO: We can stop updating the unecessary hashes once the CertVerify message is processed in case where we do Client Side Auth, or .
+
+    if(ctx->negProtocolVersion == SSL_Version_Undetermined)
+    {
+        // we dont know yet, so we might need MD5 & SHA1 - Server should always call in with known protocol version.
+        assert(ctx->protocolSide==kSSLClientSide);
+        do_md5 = do_sha1 = true;
+        if(ctx->isDTLS
+           ? ctx->maxProtocolVersion < DTLS_Version_1_0
+           : ctx->maxProtocolVersion >= TLS_Version_1_2)
+        {
+            // We wil need those too, unless we are sure we wont end up doing TLS 1.2
+            do_sha256 = do_sha384 = true;
+        }
+    } else {
+        // we know which version we use at this point
+        if(sslVersionIsLikeTls12(ctx)) {
+            do_sha1 = do_sha256 = do_sha384 = true;
+        } else {
+            do_md5 = do_sha1 = true;
+        }
+    }
+
+    if (do_md5 &&
+        (err = SSLHashMD5.update(&ctx->md5State, messageData)) != 0)
+        goto done;
+    if (do_sha1 &&
+        (err = SSLHashSHA1.update(&ctx->shaState, messageData)) != 0)
+        goto done;
+    if (do_sha256 &&
+        (err = SSLHashSHA256.update(&ctx->sha256State, messageData)) != 0)
+        goto done;
+    if (do_sha384 &&
+        (err = SSLHashSHA384.update(&ctx->sha512State, messageData)) != 0)
+        goto done;
+
+    sslLogNegotiateDebug("%s protocol: %02X max: %02X cipher: %02X%s%s",
+                         ctx->protocolSide == kSSLClientSide ? "client" : "server",
+                         ctx->negProtocolVersion,
+                         ctx->maxProtocolVersion,
+                         ctx->selectedCipher,
+                         do_md5 ? " md5" : "",
+                         do_sha1 ? " sha1" : "",
+                         do_sha256 ? " sha256" : "",
+                         do_sha384 ? " sha384" : "");
+done:
+    return err;
+}
+
+OSStatus
+SSLProcessHandshakeRecord(SSLRecord rec, SSLContext *ctx)
+{   OSStatus        err;
+    size_t          remaining;
+    UInt8           *p;
+       UInt8                   *startingP;             // top of record we're parsing
+    SSLHandshakeMsg message = {};
+    SSLBuffer       messageData;
+
+    if (ctx->fragmentedMessageCache.data != 0)
+    {
+               size_t origLen = ctx->fragmentedMessageCache.length;
+               if ((err = SSLReallocBuffer(&ctx->fragmentedMessageCache,
+                    ctx->fragmentedMessageCache.length + rec.contents.length,
+                    ctx)) != 0)
+        {   SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+            return err;
+        }
+        memcpy(ctx->fragmentedMessageCache.data + origLen,
+            rec.contents.data, rec.contents.length);
+        remaining = ctx->fragmentedMessageCache.length;
+        p = ctx->fragmentedMessageCache.data;
+    }
+    else
+    {   remaining = rec.contents.length;
+        p = rec.contents.data;
+    }
+       startingP = p;
+
+    size_t head = 4;
+
+    while (remaining > 0)
+    {
+        if (remaining < head)
+            break;  /* we must have at least a header */
+
+        messageData.data = p;
+        message.type = (SSLHandshakeType)*p++;
+        message.contents.length = SSLDecodeSize(p, 3);
+
+
+        p += 3;
+
+        if ((message.contents.length + head) > remaining)
+            break;
+
+        message.contents.data = p;
+        p += message.contents.length;
+        messageData.length = head + 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 = SSLUpdateHandshakeMacs(&messageData, ctx)) != 0)
+            {   SSLFatalSessionAlert(SSL_AlertInternalError, 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_AlertInternalError, ctx);
+                return err;
+            }
+        }
+        if (startingP != ctx->fragmentedMessageCache.data)
+        {   memcpy(ctx->fragmentedMessageCache.data, startingP, remaining);
+            ctx->fragmentedMessageCache.length = remaining;
+        }
+    }
+    else if (ctx->fragmentedMessageCache.data != 0)
+    {   if ((err = SSLFreeBuffer(&ctx->fragmentedMessageCache, ctx)) != 0)
+        {   SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+            return err;
+        }
+    }
+
+    return noErr;
+}
+
+OSStatus
+DTLSProcessHandshakeRecord(SSLRecord rec, SSLContext *ctx)
+{   OSStatus        err = errSecParam;
+    size_t          remaining;
+    UInt8           *p;
+       UInt8                   *startingP;             // top of record we're parsing
+
+    const UInt32 head = 12;
+
+    assert(ctx->isDTLS);
+
+    remaining = rec.contents.length;
+    p = rec.contents.data;
+       startingP = p;
+
+    while (remaining > 0)
+    {
+        UInt8 msgtype;
+        UInt32 msglen;
+        UInt32 msgseq;
+        UInt32 fraglen;
+        UInt32 fragofs;
+
+        if (remaining < head) {
+            /* flush it - record is too small */
+            sslErrorLog("DTLSProcessHandshakeRecord: remaining too small (%lu out of %lu)\n", remaining, rec.contents.length);
+            assert(0); // keep this assert until we find a test case that triggers it
+            goto flushit;
+        }
+
+        /* Thats the 12 bytes of header : */
+        msgtype = (SSLHandshakeType)*p++;
+        msglen = SSLDecodeInt(p, 3); p+=3;
+        msgseq = SSLDecodeInt(p, 2); p+=2;
+        fragofs = SSLDecodeInt(p, 3); p+=3;
+        fraglen = SSLDecodeInt(p, 3); p+=3;
+
+        remaining -= head;
+
+        SSLLogHdskMsg(msgtype, 0);
+        sslHdskMsgDebug("DTLS Hdsk Record: type=%lu, len=%lu, seq=%lu (%lu), f_ofs=%lu, f_len=%lu, remaining=%lu",
+                             msgtype, msglen, msgseq, ctx->hdskMessageSeqNext, fragofs, fraglen, remaining);
+
+        if(
+           ((fraglen+fragofs) > msglen)
+           || (fraglen > remaining)
+           || (msgseq!=ctx->hdskMessageSeqNext)
+           || (fragofs!=ctx->hdskMessageCurrentOfs)
+           || (fragofs && (msgtype!=ctx->hdskMessageCurrent.type))
+           || (fragofs && (msglen != ctx->hdskMessageCurrent.contents.length))
+           )
+        {
+            sslErrorLog("DTLSProcessHandshakeRecord: wrong fragment\n");
+            // assert(0); // keep this assert until we find a test case that triggers it
+            // This is a recoverable error, we just drop this fragment.
+            // TODO: this should probably trigger a retransmit
+            err = noErr;
+            goto flushit;
+        }
+
+        /* First fragment - allocate */
+        if(fragofs==0) {
+            sslHdskMsgDebug("Allocating hdsk buf for msg type %d", msgtype);
+            assert(ctx->hdskMessageCurrent.contents.data==NULL);
+            assert(ctx->hdskMessageCurrent.contents.length==0);
+            if((err=SSLAllocBuffer(&(ctx->hdskMessageCurrent.contents), msglen, ctx))!=0) {
+                SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+                return err;
+            }
+            ctx->hdskMessageCurrent.type = msgtype;
+        }
+
+        /* We have the next fragment, lets save it */
+        memcpy(ctx->hdskMessageCurrent.contents.data + ctx->hdskMessageCurrentOfs, p, fraglen);
+        ctx->hdskMessageCurrentOfs+=fraglen;
+        p+=fraglen;
+        remaining-=fraglen;
+
+        /* This was the last fragment, lets process the message */
+        if(ctx->hdskMessageCurrentOfs == ctx->hdskMessageCurrent.contents.length) {
+            err = SSLProcessHandshakeMessage(ctx->hdskMessageCurrent, ctx);
+            if(err)
+                goto flushit;
+
+            if ((msgtype != SSL_HdskHelloRequest) && (msgtype != SSL_HdskHelloVerifyRequest))
+            {
+                /* We need to hash a fake header as if no fragmentation */
+                uint8_t pseudo_header[head];
+                SSLBuffer header;
+                header.data=pseudo_header;
+                header.length=head;
+
+                pseudo_header[0]=msgtype;
+                SSLEncodeInt(pseudo_header+1, msglen, 3);
+                SSLEncodeInt(pseudo_header+4, msgseq, 2);
+                SSLEncodeInt(pseudo_header+6, 0, 3);
+                SSLEncodeInt(pseudo_header+9, msglen, 3);
+
+                if ((err = SSLHashSHA1.update(&ctx->shaState, &header)) != 0 ||
+                    (err = SSLHashMD5.update(&ctx->md5State, &header)) != 0)
+                {
+                    SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+                    goto flushit;
+                }
+
+                SSLBuffer *messageData=&ctx->hdskMessageCurrent.contents;
+                if ((err = SSLHashSHA1.update(&ctx->shaState, messageData)) != 0 ||
+                    (err = SSLHashMD5.update(&ctx->md5State, messageData)) != 0)
+                {
+                    SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+                    goto flushit;
+                }
+
+                sslHdskMsgDebug("Hashing %d bytes of msg seq %ld\n", messageData->length, msgseq);
+            }
+
+            sslHdskMsgDebug("processed message of type %d", msgtype);
+
+            if ((err = SSLAdvanceHandshake(msgtype, ctx)) != 0)
+            {
+                sslErrorLog("AdvanceHandshake error: %"PRIstatus"\n", err);
+                goto flushit;
+            }
+
+            /* Free the buffer for current message, and reset offset */
+            SSLFreeBuffer(&(ctx->hdskMessageCurrent.contents), ctx);
+            ctx->hdskMessageCurrentOfs=0;
+
+            /* If we successfully processed a message, we wait for the next one */
+            ctx->hdskMessageSeqNext++;
+
+        }
+
+        sslHdskMsgDebug("remaining = %ld", remaining);
+    }
+
+    return noErr;
+
+flushit:
+    sslErrorLog("DTLSProcessHandshakeRecord: flusing record (err=%"PRIstatus")\n", err);
+
+    /* This will flush the current handshake message */
+    SSLFreeBuffer(&(ctx->hdskMessageCurrent.contents), ctx);
+    ctx->hdskMessageCurrentOfs=0;
+
+    return err;
+}
+
+OSStatus
+DTLSRetransmit(SSLContext *ctx)
+{
+    sslHdskMsgDebug("DTLSRetransmit in state %s. Last Sent = %d, Last Recv=%d, timeout=%f\n",
+                hdskStateToStr(ctx->state), ctx->hdskMessageSeq, ctx->hdskMessageSeqNext, ctx->timeout_duration);
+
+    /* Too many retransmits, just give up!! */
+    if(ctx->hdskMessageRetryCount>10)
+        return errSSLConnectionRefused;
+
+    /* go back to previous cipher if retransmitting a flight including changecipherspec */
+    if(ctx->messageQueueContainsChangeCipherSpec) {
+        ctx->writePending = ctx->writeCipher;
+        ctx->writeCipher = ctx->prevCipher;
+    }
+
+    /* set timeout deadline */
+    ctx->hdskMessageRetryCount++;
+    ctx->timeout_deadline = CFAbsoluteTimeGetCurrent()+((1<<ctx->hdskMessageRetryCount)*ctx->timeout_duration);
+
+    /* Lets resend the last flight */
+    return SSLSendFlight(ctx);
+}
+
+static OSStatus
+SSLProcessHandshakeMessage(SSLHandshakeMsg message, SSLContext *ctx)
+{   OSStatus      err;
+
+    err = noErr;
+    SSLLogHdskMsg(message.type, 0);
+    switch (message.type)
+    {   case SSL_HdskHelloRequest:
+            if (ctx->protocolSide != kSSLClientSide)
+                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;
+#if ENABLE_DTLS
+        case SSL_HdskHelloVerifyRequest:
+            if (ctx->protocolSide != kSSLClientSide)
+                goto wrongMessage;
+            if(ctx->state != SSL_HdskStateServerHello)
+                goto wrongMessage;
+            /* TODO: Do we need to check the client state here ? */
+            err = SSLProcessServerHelloVerifyRequest(message.contents, ctx);
+            break;
+#endif
+        case SSL_HdskCert:
+            if (ctx->state != SSL_HdskStateCert &&
+                ctx->state != SSL_HdskStateClientCert)
+                goto wrongMessage;
+                       err = SSLProcessCertificate(message.contents, ctx);
+                       /*
+                        * Note that cert evaluation can now be performed asynchronously,
+                        * so SSLProcessCertificate may return errSSLWouldBlock here.
+                        */
+            break;
+        case SSL_HdskCertRequest:
+            if (((ctx->state != SSL_HdskStateHelloDone) &&
+                            (ctx->state != SSL_HdskStateKeyExchange))
+                 || ctx->certRequested)
+                goto wrongMessage;
+            err = SSLProcessCertificateRequest(message.contents, ctx);
+            if (ctx->breakOnCertRequest)
+                ctx->signalCertRequest = true;
+            break;
+        case SSL_HdskServerKeyExchange:
+                       /*
+                * Since this message is optional for some key exchange
+                        * mechanisms, 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 == kSSLServerSide);
+                       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 && !ctx->sentFatalAlert)
+    {   if (err == errSSLProtocol)
+            SSLFatalSessionAlert(SSL_AlertIllegalParam, ctx);
+        else if (err == errSSLNegotiation)
+            SSLFatalSessionAlert(SSL_AlertHandshakeFail, ctx);
+        else if (err != errSSLWouldBlock &&
+                                err != errSSLServerAuthCompleted /* == errSSLClientAuthCompleted */ &&
+                                err != errSSLClientCertRequested)
+            SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+    }
+    return err;
+
+wrongMessage:
+    SSLFatalSessionAlert(SSL_AlertUnexpectedMsg, ctx);
+    return errSSLProtocol;
+}
+
+/*
+ * Given a server-side SSLContext that's fully restored for a resumed session,
+ * queue up the remaining outgoing messages to finish the handshake.
+ */
+static OSStatus
+SSLResumeServerSide(
+       SSLContext *ctx)
+{
+       OSStatus err;
+       if ((err = SSLPrepareAndQueueMessage(SSLEncodeServerHello, ctx)) != 0)
+               return err;
+       if ((err = SSLInitPendingCiphers(ctx)) != 0)
+       {   SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+               return err;
+       }
+       if ((err = SSLPrepareAndQueueMessage(SSLEncodeChangeCipherSpec,
+                               ctx)) != 0)
+               return err;
+
+       if ((err = SSLPrepareAndQueueMessage(SSLEncodeFinishedMessage,
+                       ctx)) != 0)
+               return err;
+
+       SSLChangeHdskState(ctx, SSL_HdskStateChangeCipherSpec);
+
+       return noErr;
+
+}
+
+OSStatus
+SSLAdvanceHandshake(SSLHandshakeType processed, SSLContext *ctx)
+{   OSStatus        err;
+    SSLBuffer       sessionIdentifier;
+
+    SSLResetFlight(ctx);
+
+    switch (processed)
+    {
+#if ENABLE_DTLS
+        case SSL_HdskHelloVerifyRequest:
+            /* Just fall through */
+#endif
+        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;
+                       ctx->readCipher.ready = 0;
+                       ctx->writeCipher.ready = 0;
+                       ctx->wroteAppData = 0;
+            if ((err = SSLPrepareAndQueueMessage(SSLEncodeClientHello, ctx)) != 0)
+                return err;
+            SSLChangeHdskState(ctx, SSL_HdskStateServerHello);
+            break;
+        case SSL_HdskClientHello:
+            assert(ctx->protocolSide == kSSLServerSide);
+                       ctx->sessionMatch = 0;
+
+            if((ctx->negProtocolVersion==DTLS_Version_1_0) && (ctx->cookieVerified==false))
+            {   /* Send Hello Verify Request */
+                if((err=SSLPrepareAndQueueMessage(SSLEncodeServerHelloVerifyRequest, ctx)) !=0 )
+                    return err;
+                break;
+            }
+
+                       #if     SSL_PAC_SERVER_ENABLE
+                       if((ctx->sessionTicket.data != NULL) &&
+                          (ctx->masterSecretCallback != NULL)) {
+                               /*
+                                * Client sent us a session ticket and we know how to ask
+                                * the app for master secret. Go for it.
+                                */
+                               size_t secretLen = SSL_MASTER_SECRET_SIZE;
+                               sslEapDebug("Server side resuming based on masterSecretCallback");
+
+                               /* the master secret callback requires serverRandom, now... */
+                           if ((err = SSLEncodeRandom(ctx->serverRandom, ctx)) != 0)
+                                        return err;
+                               ctx->serverRandomValid = 1;
+
+                               ctx->masterSecretCallback(ctx, ctx->masterSecretArg,
+                                       ctx->masterSecret, &secretLen);
+                               ctx->sessionMatch = 1;
+                               /* set up selectedCipherSpec */
+                               if ((err = FindCipherSpec(ctx)) != 0) {
+                                       return err;
+                               }
+                               /* queue up remaining messages to finish handshake */
+                               if((err = SSLResumeServerSide(ctx)) != 0)
+                                       return err;
+                               break;
+                       }
+                       #endif  /* SSL_PAC_SERVER_ENABLE */
+            if (ctx->sessionID.data != 0)
+                       /* If session ID != 0, client is trying to resume */
+            {   if (ctx->resumableSession.data != 0)
+                {
+                                       SSLProtocolVersion sessionProt;
+                                       if ((err = SSLRetrieveSessionID(ctx->resumableSession,
+                                                               &sessionIdentifier, ctx)) != 0)
+                        return err;
+                                       if ((err = SSLRetrieveSessionProtocolVersion(ctx->resumableSession,
+                                                       &sessionProt, ctx)) != 0)
+                                       {   SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+                                               return err;
+                                       }
+                                       if ((sessionIdentifier.length == ctx->sessionID.length) &&
+                        (memcmp(sessionIdentifier.data, ctx->sessionID.data,
+                                                       ctx->sessionID.length) == 0) &&
+                                           (sessionProt == ctx->negProtocolVersion))
+                    {   /* Everything matches; resume the session */
+                                               sslLogResumSessDebug("===RESUMING SSL3 server-side session");
+                        if ((err = SSLInstallSessionFromData(ctx->resumableSession,
+                                                               ctx)) != 0)
+                        {   SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+                            return err;
+                        }
+                                               ctx->sessionMatch = 1;
+                                               SSLFreeBuffer(&sessionIdentifier, ctx);
+
+                                               /* queue up remaining messages to finish handshake */
+                                               if((err = SSLResumeServerSide(ctx)) != 0)
+                                                       return err;
+                        break;
+                    }
+                                       else {
+                                               sslLogResumSessDebug(
+                                                       "===FAILED TO RESUME SSL3 server-side session");
+                                       }
+                    if ((err = SSLFreeBuffer(&sessionIdentifier, ctx)) != 0 ||
+                        (err = SSLDeleteSessionData(ctx)) != 0)
+                    {   SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+                        return err;
+                    }
+                }
+                if ((err = SSLFreeBuffer(&ctx->sessionID, ctx)) != 0)
+                {   SSLFatalSessionAlert(SSL_AlertInternalError, 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_AlertInternalError, 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(ctx->localCert == NULL) {
+                                               /* no cert but configured for, and negotiated, a
+                                                * ciphersuite which requires one */
+                                               sslErrorLog("SSLAdvanceHandshake: No server key!\n");
+                                               return errSSLBadConfiguration;
+                                       }
+                    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.
+                        */
+                       {
+                               bool doServerKeyExch = false;
+                               switch(ctx->selectedCipherSpec.keyExchangeMethod) {
+                                       case SSL_RSA_EXPORT:
+                                       #if !SSL_SERVER_KEYEXCH_HACK
+                                       /* the "proper" way - app decides. */
+                                       case SSL_RSA:
+                                       #endif
+                                               if(ctx->encryptPrivKeyRef != NULL) {
+                                                       doServerKeyExch = true;
+                                               }
+                                               break;
+                                       case SSL_DH_anon:
+                                       case SSL_DH_anon_EXPORT:
+                                       case SSL_DHE_RSA:
+                                       case SSL_DHE_RSA_EXPORT:
+                                       case SSL_DHE_DSS:
+                                       case SSL_DHE_DSS_EXPORT:
+                                               doServerKeyExch = true;
+                                               break;
+                                       default:
+                                               break;
+                               }
+                               if(doServerKeyExch) {
+                                       err = SSLPrepareAndQueueMessage(SSLEncodeServerKeyExchange, ctx);
+                                       if(err) {
+                                               return err;
+                                       }
+                               }
+                       }
+            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:
+                       ctx->sessionMatch = 0;
+            if (ctx->resumableSession.data != 0 && ctx->sessionID.data != 0)
+            {
+                               SSLProtocolVersion sessionProt;
+                               if ((err = SSLRetrieveSessionID(ctx->resumableSession,
+                                               &sessionIdentifier, ctx)) != 0)
+                {   SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+                    return err;
+                }
+                               if ((err = SSLRetrieveSessionProtocolVersion(ctx->resumableSession,
+                                               &sessionProt, ctx)) != 0)
+                {   SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+                    return err;
+                }
+                if ((sessionIdentifier.length == ctx->sessionID.length) &&
+                    (memcmp(sessionIdentifier.data, ctx->sessionID.data,
+                                                       ctx->sessionID.length) == 0) &&
+                                       (sessionProt == ctx->negProtocolVersion))
+                {   /* 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_AlertInternalError, ctx);
+                        return err;
+                    }
+                                       ctx->sessionMatch = 1;
+                    SSLChangeHdskState(ctx, SSL_HdskStateChangeCipherSpec);
+                    break;
+                }
+                               else {
+                                       sslLogResumSessDebug("===FAILED TO RESUME SSL3 client-side "
+                                                       "session");
+                               }
+                if ((err = SSLFreeBuffer(&sessionIdentifier, ctx)) != 0)
+                {   SSLFatalSessionAlert(SSL_AlertInternalError, 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:
+                               case SSL_ECDH_ECDSA:
+                               case SSL_ECDHE_ECDSA:
+                               case SSL_ECDH_RSA:
+                               case SSL_ECDHE_RSA:
+                    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:
+                                       case SSL_ECDH_ECDSA:
+                                       case SSL_ECDH_RSA:
+                        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:
+                                       case SSL_ECDHE_ECDSA:
+                                       case SSL_ECDHE_RSA:
+                        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;
+            }
+            assert(ctx->protocolSide == kSSLClientSide);
+            ctx->certRequested = 1;
+            ctx->clientCertState = kSSLClientCertRequested;
+            break;
+        case SSL_HdskServerKeyExchange:
+            SSLChangeHdskState(ctx, SSL_HdskStateHelloDone);
+            break;
+        case SSL_HdskServerHelloDone:
+                       /*
+             * Waiting until server has sent hello done to interrupt and allow
+             * setting client cert, so we can send certificate, keyexchange and
+             * cert verify message together
+                        */
+            if (ctx->state != SSL_HdskStateClientCert) {
+                if (ctx->signalServerAuth) {
+                    ctx->signalServerAuth = false;
+                    SSLChangeHdskState(ctx, SSL_HdskStateClientCert);
+                    return errSSLServerAuthCompleted;
+                } else if (ctx->signalCertRequest) {
+                    ctx->signalCertRequest = false;
+                    SSLChangeHdskState(ctx, SSL_HdskStateClientCert);
+                    return errSSLClientCertRequested;
+                } else if (ctx->signalClientAuth) {
+                    ctx->signalClientAuth = false;
+                    return errSSLClientAuthCompleted;
+                }
+            }
+
+                       if (ctx->clientCertState == kSSLClientCertRequested) {
+                               /*
+                                * 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_RESERVED,
+                                                               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_AlertInternalError, ctx);
+                return err;
+            }
+                       memset(ctx->preMasterSecret.data, 0, ctx->preMasterSecret.length);
+            if ((err = SSLFreeBuffer(&ctx->preMasterSecret, ctx)) != 0) {
+                return err;
+                       }
+            if (ctx->certSent) {
+                               /* Not all client auth mechanisms require a cert verify message */
+                               switch(ctx->negAuthType) {
+                                       case SSLClientAuth_RSASign:
+                                       case SSLClientAuth_ECDSASign:
+                                               if ((err = SSLPrepareAndQueueMessage(SSLEncodeCertificateVerify,
+                                                               ctx)) != 0) {
+                                                       return err;
+                                               }
+                                               break;
+                                       default:
+                                               break;
+                               }
+                       }
+            if ((err = SSLPrepareAndQueueMessage(SSLEncodeChangeCipherSpec,
+                                       ctx)) != 0) {
+                return err;
+                       }
+            if ((err = SSLPrepareAndQueueMessage(SSLEncodeFinishedMessage, ctx)) != 0)
+                return err;
+            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_AlertInternalError, 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;
+                if ((err = SSLPrepareAndQueueMessage(SSLEncodeFinishedMessage,
+                                                       ctx)) != 0)
+                    return err;
+            }
+            if (ctx->protocolSide == kSSLServerSide) {
+                SSLChangeHdskState(ctx, SSL_HdskStateServerReady);
+            }
+            else {
+                SSLChangeHdskState(ctx, SSL_HdskStateClientReady);
+            }
+            if ((ctx->peerID.data != 0) && (ctx->sessionTicket.data == NULL)) {
+                               /* note we avoid caching session data for PAC-style resumption */
+                SSLAddSessionData(ctx);
+                       }
+            break;
+        default:
+            assert(0);
+            break;
+    }
+
+    /* We should have a full flight when we reach here, sending it for the first time */
+    ctx->hdskMessageRetryCount = 0;
+    ctx->timeout_deadline = CFAbsoluteTimeGetCurrent() + ctx->timeout_duration;
+    return SSLSendFlight(ctx);
+}
+
+OSStatus
+SSLPrepareAndQueueMessage(EncodeMessageFunc msgFunc, SSLContext *ctx)
+{   OSStatus        err;
+    SSLRecord       rec = {0, 0, {0, NULL}};
+    WaitingMessage  *out;
+    WaitingMessage  *queue;
+
+    if ((err = msgFunc(&rec, ctx)) != 0)
+    {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+        goto fail;
+    }
+
+    if (rec.contentType == SSL_RecordTypeHandshake)
+    {
+        if ((err = SSLUpdateHandshakeMacs(&rec.contents, ctx)) != 0)
+        {   SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+            goto fail;
+        }
+        SSLLogHdskMsg((SSLHandshakeType)rec.contents.data[0], 1);
+        ctx->hdskMessageSeq++;
+    }
+
+    err=errSSLInternal;
+    out = (WaitingMessage *)sslMalloc(sizeof(WaitingMessage));
+    if(out==NULL) goto fail;
+
+    out->next = NULL;
+       out->rec = rec;
+
+    queue=ctx->messageWriteQueue;
+    if (queue == NULL) {
+        sslHdskMsgDebug("Queuing first message in flight\n");
+        ctx->messageWriteQueue = out;
+    } else {
+        int n=1;
+        while (queue->next != 0) {
+            queue = queue->next;
+            n++;
+        }
+        sslHdskMsgDebug("Queuing message %d in flight\n", n);
+        queue->next = out;
+    }
+
+    return noErr;
+fail:
+    SSLFreeBuffer(&rec.contents, ctx);
+    return err;
+}
+
+static
+OSStatus SSLSendMessage(SSLRecord rec, SSLContext *ctx)
+{
+    OSStatus err;
+
+    assert(ctx->sslTslCalls != NULL);
+
+    if ((err = ctx->sslTslCalls->writeRecord(rec, ctx)) != 0)
+        return err;
+    if(rec.contentType == SSL_RecordTypeChangeCipher) {
+        /* Install new cipher spec on write side */
+        if ((err = SSLDisposeCipherSuite(&ctx->writeCipher, ctx)) != 0)
+        {   SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+            return err;
+        }
+        ctx->prevCipher = ctx->writeCipher;
+        ctx->writeCipher = ctx->writePending;
+        /* Can't send data until Finished is sent */
+        ctx->writeCipher.ready = 0;
+               ctx->wroteAppData = 0;
+
+        /* Zero out old data */
+        memset(&ctx->writePending, 0, sizeof(CipherContext));
+        ctx->writePending.encrypting = 1;
+
+        /* TODO: that should only happen after Finished message is sent. <rdar://problem/9682471> */
+        ctx->writeCipher.ready = 1;
+    }
+
+    return noErr;
+}
+
+static
+OSStatus DTLSSendMessage(SSLRecord rec, SSLContext *ctx)
+{
+    OSStatus err=noErr;
+
+    assert(ctx->sslTslCalls != NULL);
+
+    if(rec.contentType != SSL_RecordTypeHandshake) {
+        sslHdskMsgDebug("Not fragmenting message type=%d len=%d\n", rec.contentType, rec.contents.length);
+        if ((err = ctx->sslTslCalls->writeRecord(rec, ctx)) != 0)
+            return err;
+        if(rec.contentType == SSL_RecordTypeChangeCipher) {
+            /* Install new cipher spec on write side */
+            if ((err = SSLDisposeCipherSuite(&ctx->writeCipher, ctx)) != 0)
+            {   SSLFatalSessionAlert(SSL_AlertInternalError, ctx);
+                return err;
+            }
+            ctx->prevCipher = ctx->writeCipher;
+            ctx->writeCipher = ctx->writePending;
+                       /* Can't send data until Finished is sent */
+            ctx->writeCipher.ready = 0;
+                       ctx->wroteAppData = 0;
+
+                       /* Zero out old data */
+            memset(&ctx->writePending, 0, sizeof(CipherContext));
+                       ctx->writePending.encrypting = 1;
+
+            /* TODO: that should only happen after Finished message is sent. See <rdar://problem/9682471> */
+            ctx->writeCipher.ready = 1;
+
+        }
+    } else {
+    /* fragmenting */
+        SSLRecord fragrec;
+
+        int msghead = 12;  /* size of message header in DTLS */
+        size_t fraglen;
+        size_t len = rec.contents.length-msghead;
+        UInt32 seq = SSLDecodeInt(rec.contents.data+4, 2);
+        (void) seq; // Suppress warnings
+        size_t ofs = 0;
+
+        sslHdskMsgDebug("Fragmenting msg seq %ld (rl=%d, ml=%d)", seq, rec.contents.length,
+                        SSLDecodeInt(rec.contents.data+1, 3));
+
+
+        SSLGetDatagramWriteSize(ctx, &fraglen);
+        fraglen -=  msghead;
+
+        fragrec.contentType = rec.contentType;
+        fragrec.protocolVersion = rec.protocolVersion;
+        if((err=SSLAllocBuffer(&fragrec.contents, fraglen + msghead, ctx))!=0)
+            return err;
+
+        /* copy the constant part of the header */
+        memcpy(fragrec.contents.data,rec.contents.data, 6);
+
+        while(len>fraglen) {
+
+            sslHdskMsgDebug("Fragmenting msg seq %ld (o=%d,l=%d)", seq, ofs, fraglen);
+
+            /* fragment offset and fragment length */
+            SSLEncodeSize(fragrec.contents.data+6, ofs, 3);
+            SSLEncodeSize(fragrec.contents.data+9, fraglen, 3);
+            /* copy the payload */
+            memcpy(fragrec.contents.data+msghead, rec.contents.data+msghead+ofs, fraglen);
+            if ((err = ctx->sslTslCalls->writeRecord(fragrec, ctx)) != 0)
+                goto cleanup;
+            len-=fraglen;
+            ofs+=fraglen;
+        }
+
+        sslHdskMsgDebug("Fragmenting msg seq %ld - Last Fragment (o=%d,l=%d)", seq, ofs, len);
+
+        /* last fragment */
+        /* fragment offset and fragment length */
+        SSLEncodeSize(fragrec.contents.data+6, ofs, 3);
+        SSLEncodeSize(fragrec.contents.data+9, len, 3);
+        /* copy the payload */
+        memcpy(fragrec.contents.data+msghead, rec.contents.data+msghead+ofs, len);
+        fragrec.contents.length=len+msghead;
+        err = ctx->sslTslCalls->writeRecord(fragrec, ctx);
+
+    cleanup:
+        /* Free the allocated fragment buffer */
+        SSLFreeBuffer(&fragrec.contents, ctx);
+
+    }
+
+    return err;
+}
+
+
+OSStatus SSLResetFlight(SSLContext *ctx)
+{
+    OSStatus err;
+    WaitingMessage *queue;
+    WaitingMessage *next;
+    int n=0;
+
+    assert(ctx->sslTslCalls != NULL);
+
+    queue=ctx->messageWriteQueue;
+    ctx->messageQueueContainsChangeCipherSpec=false;
+
+    while(queue) {
+        n++;
+        err = SSLFreeBuffer(&queue->rec.contents, ctx);
+        if (err != 0)
+            goto fail;
+        next=queue->next;
+        sslFree(queue);
+        queue=next;
+    }
+
+    ctx->messageWriteQueue=NULL;
+
+    return noErr;
+fail:
+    assert(0);
+    return err;
+}
+
+
+OSStatus SSLSendFlight(SSLContext *ctx)
+{
+    OSStatus err;
+    WaitingMessage  *queue;
+    int n=0;
+
+    assert(ctx->sslTslCalls != NULL);
+
+    queue=ctx->messageWriteQueue;
+
+    while(queue) {
+        if (ctx->isDTLS) {
+            err=DTLSSendMessage(queue->rec, ctx);
+        } else {
+            err=SSLSendMessage(queue->rec, ctx);
+        }
+        if (err != 0)
+            goto fail;
+        queue=queue->next;
+        n++;
+    }
+
+    return noErr;
+fail:
+    assert(0);
+    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_AlertInternalError, 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 SSL_HdskStateServerReady:
+                       return "SSL_ServerReady";
+               case SSL_HdskStateClientReady:
+                       return "SSL_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_HdskHelloVerifyRequest:
+                       return "SSL_HdskHelloVerifyRequest";
+               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";
+               default:
+                       sprintf(badStr, "Unknown msg (%d(d))", msg);
+                       return badStr;
+       }
+}
+
+void SSLLogHdskMsg(SSLHandshakeType msg, char sent)
+{
+       sslHdskMsgDebug("---%s handshake msg %s",
+               hdskMsgToStr(msg), (sent ? "sent" : "recv"));
+}
+
+#endif /* NDEBUG */
+