]> git.saurik.com Git - apple/security.git/blobdiff - SecureTransport/tls1Callouts.cpp
Security-54.1.3.tar.gz
[apple/security.git] / SecureTransport / tls1Callouts.cpp
diff --git a/SecureTransport/tls1Callouts.cpp b/SecureTransport/tls1Callouts.cpp
new file mode 100644 (file)
index 0000000..a1c4c30
--- /dev/null
@@ -0,0 +1,688 @@
+/*
+ * Copyright (c) 2002 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:           tls1Callouts.c
+
+       Contains:       TLSv1-specific routines for SslTlsCallouts. 
+
+       Written by:     Doug Mitchell
+*/
+
+#include "tls_ssl.h"
+#include "sslMemory.h"
+#include "sslUtils.h"
+#include "sslDigests.h"
+#include "sslAlertMessage.h"
+#include "sslDebug.h"
+#include <assert.h>
+#include <strings.h>
+
+#define TLS_ENC_DEBUG          0
+#if            TLS_ENC_DEBUG
+#define tlsDebug(format, args...)      printf(format , ## args)
+static void tlsDump(const char *name, void *b, unsigned len)
+{
+       unsigned char *cp = (unsigned char *)b;
+       unsigned i, dex;
+       
+       printf("%s\n", name);
+       for(dex=0; dex<len; dex++) {
+               i = cp[dex];
+               printf("%02X ", i);
+               if((dex % 16) == 15) {
+                       printf("\n");
+               }
+       }
+       printf("\n");
+}
+
+#else
+#define tlsDebug(s, ...)
+#define tlsDump(name, b, len)
+#endif /* TLS_ENC_DEBUG */
+
+#pragma *** PRF label strings ***
+/*
+ * Note we could optimize away a bunch of mallocs and frees if we, like openSSL,
+ * just mallocd buffers for inputs to SSLInternal_PRF() on the stack, 
+ * with "known" max values for all of the inputs. 
+ *
+ * At least we hard-code string lengths here instead of calling strlen at runtime...
+ */
+#define PLS_MASTER_SECRET                      "master secret"
+#define PLS_MASTER_SECRET_LEN          13
+#define PLS_KEY_EXPAND                         "key expansion"
+#define PLS_KEY_EXPAND_LEN                     13
+#define PLS_CLIENT_FINISH                      "client finished"
+#define PLS_CLIENT_FINISH_LEN          15
+#define PLS_SERVER_FINISH                      "server finished"
+#define PLS_SERVER_FINISH_LEN          15
+#define PLS_EXPORT_CLIENT_WRITE                "client write key"
+#define PLS_EXPORT_CLIENT_WRITE_LEN    16
+#define PLS_EXPORT_SERVER_WRITE                "server write key"
+#define PLS_EXPORT_SERVER_WRITE_LEN    16
+#define PLS_EXPORT_IV_BLOCK                    "IV block"
+#define PLS_EXPORT_IV_BLOCK_LEN                8
+
+#pragma mark *** private functions ***
+
+/*
+ * P_Hash function defined in RFC2246, section 5. 
+ */
+static OSStatus tlsPHash(
+       SSLContext                      *ctx,
+       const HMACReference *hmac,              // &TlsHmacSHA1, TlsHmacMD5
+       const unsigned char *secret,
+       unsigned                        secretLen, 
+       unsigned char           *seed, 
+       unsigned                        seedLen,
+       unsigned char           *out,           // mallocd by caller, size >= outLen
+       unsigned                        outLen)         // desired output size
+{
+       unsigned char aSubI[TLS_HMAC_MAX_SIZE];         /* A(i) */
+       unsigned char digest[TLS_HMAC_MAX_SIZE];
+       HMACContextRef hmacCtx;
+       OSStatus serr;
+       unsigned digestLen = hmac->macSize;
+       
+       serr = hmac->alloc(hmac, ctx, secret, secretLen, &hmacCtx);
+       if(serr) {
+               return serr;
+       }
+       
+       /* A(0) = seed */
+       /* A(1) := HMAC_hash(secret, seed) */
+       serr = hmac->hmac(hmacCtx, seed, seedLen, aSubI, &digestLen); 
+       if(serr) {
+               goto fail;
+       }
+       assert(digestLen = hmac->macSize);
+       
+       /* starting at loopNum 1... */
+       for (;;) {
+               /* 
+                * This loop's chunk = HMAC_hash(secret, A(loopNum) + seed))
+                */
+               serr = hmac->init(hmacCtx);
+               if(serr) {
+                       break;
+               }
+               serr = hmac->update(hmacCtx, aSubI, digestLen);
+               if(serr) {
+                       break;
+               }
+               serr = hmac->update(hmacCtx, seed, seedLen);
+               if(serr) {
+                       break;
+               }
+               serr = hmac->final(hmacCtx, digest, &digestLen);
+               if(serr) {
+                       break;
+               }
+               assert(digestLen = hmac->macSize);
+               
+               if(outLen <= digestLen) {
+                       /* last time, possible partial digest */
+                       memmove(out, digest, outLen);
+                       break;
+               }
+               
+               memmove(out, digest, digestLen);
+               out += digestLen;
+               outLen -= digestLen;
+               
+               /* 
+                * A(i) = HMAC_hash(secret, A(i-1)) 
+                * Note there is a possible optimization involving obtaining this
+                * hmac by cloning the state of hmacCtx above after updating with
+                * aSubI, and getting the final version of that here. However CDSA
+                * does not support cloning of a MAC context (only for digest contexts). 
+                */
+               serr = hmac->hmac(hmacCtx, aSubI, digestLen,
+                       aSubI, &digestLen);
+               if(serr) {
+                       break;
+               }
+               assert(digestLen = hmac->macSize);
+       }
+fail:
+       hmac->free(hmacCtx);
+       memset(aSubI, 0, TLS_HMAC_MAX_SIZE);
+       memset(digest, 0, TLS_HMAC_MAX_SIZE);
+       return serr;
+}
+
+/*
+ * The TLS pseudorandom function, defined in RFC2246, section 5.
+ * This takes as its input a secret block, a label, and a seed, and produces
+ * a caller-specified length of pseudorandom data.
+ *
+ * Optimization TBD: make label optional, avoid malloc and two copies if it's
+ * not there, so callers can take advantage of fixed-size seeds.
+ */
+OSStatus SSLInternal_PRF(
+       SSLContext *ctx,
+       const void *vsecret,
+       size_t secretLen,
+       const void *label,                      // optional, NULL implies that seed contains
+                                                               //   the label
+       size_t labelLen,
+       const void *seed,
+       size_t seedLen,
+       void *vout,                                     // mallocd by caller, length >= outLen
+       size_t outLen)
+{
+       OSStatus serr = errSSLInternal;
+       const unsigned char *S1, *S2;           // the two seeds
+       unsigned sLen;                                          // effective length of each seed
+       unsigned char *labelSeed = NULL;        // label + seed, passed to tlsPHash
+       unsigned labelSeedLen;
+       unsigned char *tmpOut = NULL;           // output of P_SHA1
+       unsigned i;
+       const unsigned char *secret = (const unsigned char *)vsecret;
+       
+       /* two seeds for tlsPHash */
+       sLen = secretLen / 2;                   // for partitioning 
+       S1 = secret;
+       S2 = &secret[sLen];
+       sLen += (secretLen & 1);                // secret length odd, increment effective size
+       
+       if(label != NULL) {
+               /* concatenate label and seed */
+               labelSeedLen = labelLen + seedLen;
+               labelSeed = (unsigned char *)sslMalloc(labelSeedLen);
+               if(labelSeed == NULL) {
+                       return memFullErr;
+               }
+               memmove(labelSeed, label, labelLen);
+               memmove(labelSeed + labelLen, seed, seedLen);
+       }
+       else {
+               /* fast track - just use seed as is */
+               labelSeed = (unsigned char *)seed;
+               labelSeedLen = seedLen;
+       }
+       
+       /* temporary output for SHA1, to be XORd with MD5 */
+       unsigned char *out = (unsigned char *)vout;
+       tmpOut = (unsigned char *)sslMalloc(outLen);
+       if(tmpOut == NULL) {
+               serr = memFullErr;
+               goto fail;
+       }
+       serr = tlsPHash(ctx, &TlsHmacMD5, S1, sLen, labelSeed, labelSeedLen,
+               out, outLen);
+       if(serr) {
+               goto fail;
+       }
+       serr = tlsPHash(ctx, &TlsHmacSHA1, S2, sLen, labelSeed, labelSeedLen,
+               tmpOut, outLen);
+       if(serr) {
+               goto fail;
+       }
+       
+       /* XOR together to get final result */
+       for(i=0; i<outLen; i++) {
+               out[i] ^= tmpOut[i];
+       }
+       serr = noErr;
+       
+fail:
+       if((labelSeed != NULL) && (label != NULL)) {
+               sslFree(labelSeed);
+       }
+       if(tmpOut != NULL) {
+               sslFree(tmpOut);
+       }
+       return serr;
+}
+
+/* not needed; encrypt/encode is the same for both protocols as long as 
+ * we don't use the "variable length padding" feature. */
+#if 0
+static OSStatus tls1WriteRecord(
+       SSLRecord rec, 
+       SSLContext *ctx)
+{
+       assert(0);
+       return unimpErr;
+}
+#endif
+
+static OSStatus tls1DecryptRecord(
+       UInt8 type, 
+       SSLBuffer *payload, 
+       SSLContext *ctx)
+{   
+       OSStatus    err;
+    SSLBuffer   content;
+    
+    if ((ctx->readCipher.symCipher->blockSize > 0) &&
+        ((payload->length % ctx->readCipher.symCipher->blockSize) != 0)) {
+               SSLFatalSessionAlert(SSL_AlertUnexpectedMsg, ctx);
+        return errSSLProtocol;
+    }
+
+    /* Decrypt in place */
+    if ((err = ctx->readCipher.symCipher->decrypt(*payload, 
+               *payload, 
+               &ctx->readCipher, 
+               ctx)) != 0)
+    {   SSLFatalSessionAlert(SSL_AlertCloseNotify, ctx);
+        return err;
+    }
+    
+       /* Locate content within decrypted payload */
+    content.data = payload->data;
+    content.length = payload->length - ctx->readCipher.macRef->hash->digestSize;
+    if (ctx->readCipher.symCipher->blockSize > 0) {
+               /* for TLSv1, padding can be anywhere from 0 to 255 bytes */
+               UInt8 padSize = payload->data[payload->length - 1];
+               UInt8 *padChars;
+               
+               /* verify that all padding bytes are equal - WARNING - OpenSSL code
+                * has a special case here dealing with some kind of bug related to
+                * even size packets...beware... */
+               if(padSize > payload->length) {
+                       SSLFatalSessionAlert(SSL_AlertUnexpectedMsg, ctx);
+               sslErrorLog("tls1DecryptRecord: bad padding length (%d)\n", 
+                       (unsigned)payload->data[payload->length - 1]);
+            return errSSLProtocol;
+               }
+               padChars = payload->data + payload->length - padSize;
+               while(padChars < (payload->data + payload->length)) {
+                       if(*padChars++ != padSize) {
+                               SSLFatalSessionAlert(SSL_AlertUnexpectedMsg, ctx);
+                               sslErrorLog("tls1DecryptRecord: bad padding value\n");
+                               return errSSLProtocol;
+                       }
+               }
+               /* Remove block size padding and its one-byte length */
+        content.length -= (1 + padSize);
+    }
+
+       /* Verify MAC on payload */
+    if (ctx->readCipher.macRef->hash->digestSize > 0)       
+               /* Optimize away MAC for null case */
+        if ((err = SSLVerifyMac(type, content, 
+                               payload->data + content.length, ctx)) != 0)
+        {   SSLFatalSessionAlert(SSL_AlertBadRecordMac, ctx);
+            return err;
+        }
+    
+    *payload = content;     /* Modify payload buffer to indicate content length */
+    
+    return noErr;
+}
+
+/* initialize a per-CipherContext HashHmacContext for use in MACing each record */
+static OSStatus tls1InitMac (
+       CipherContext *cipherCtx,               // macRef, macSecret valid on entry
+                                                                       // macCtx valid on return
+       SSLContext *ctx)
+{
+       const HMACReference *hmac;
+       OSStatus serr;
+       
+       assert(cipherCtx->macRef != NULL);
+       hmac = cipherCtx->macRef->hmac;
+       assert(hmac != NULL);
+       
+       if(cipherCtx->macCtx.hmacCtx != NULL) {
+               hmac->free(cipherCtx->macCtx.hmacCtx);
+               cipherCtx->macCtx.hmacCtx = NULL;
+       }
+       serr = hmac->alloc(hmac, ctx, cipherCtx->macSecret, 
+               cipherCtx->macRef->hmac->macSize, &cipherCtx->macCtx.hmacCtx);
+               
+       /* mac secret now stored in macCtx.hmacCtx, delete it from cipherCtx */
+       memset(cipherCtx->macSecret, 0, sizeof(cipherCtx->macSecret));
+       return serr;
+}
+
+static OSStatus tls1FreeMac (
+       CipherContext *cipherCtx)
+{
+       /* this can be called on a completely zeroed out CipherContext... */
+       if(cipherCtx->macRef == NULL) {
+               return noErr;
+       }
+       assert(cipherCtx->macRef->hmac != NULL);
+       
+       if(cipherCtx->macCtx.hmacCtx != NULL) {
+               cipherCtx->macRef->hmac->free(cipherCtx->macCtx.hmacCtx);
+               cipherCtx->macCtx.hmacCtx = NULL;
+       }
+       return noErr;
+}
+
+/*
+ * mac = HMAC_hash(MAC_write_secret, seq_num + TLSCompressed.type +
+ *                                     TLSCompressed.version + TLSCompressed.length +
+ *                                     TLSCompressed.fragment));
+ */
+/* sequence, type, version, length */
+#define HDR_LENGTH (8 + 1 + 2 + 2)
+OSStatus tls1ComputeMac (
+       UInt8 type, 
+       SSLBuffer data,                         
+       SSLBuffer mac,                                  // caller mallocs data
+       CipherContext *cipherCtx,               // assumes macCtx, macRef
+       sslUint64 seqNo, 
+       SSLContext *ctx)
+{
+       unsigned char hdr[HDR_LENGTH];
+       unsigned char *p;
+       HMACContextRef hmacCtx;
+       OSStatus serr;
+       const HMACReference *hmac;
+       unsigned macLength;
+       
+       assert(cipherCtx != NULL);
+       assert(cipherCtx->macRef != NULL);
+       hmac = cipherCtx->macRef->hmac;
+       assert(hmac != NULL);
+       hmacCtx = cipherCtx->macCtx.hmacCtx;    // may be NULL, for null cipher
+       
+       serr = hmac->init(hmacCtx);
+       if(serr) {
+               goto fail;
+       }
+       p = SSLEncodeUInt64(hdr, seqNo);
+       *p++ = type;
+       *p++ = TLS_Version_1_0 >> 8;
+       *p++ = TLS_Version_1_0 & 0xff;
+       *p++ = data.length >> 8;
+       *p   = data.length & 0xff;
+       serr = hmac->update(hmacCtx, hdr, HDR_LENGTH);
+       if(serr) {
+               goto fail;
+       }
+       serr = hmac->update(hmacCtx, data.data, data.length);
+       if(serr) {
+               goto fail;
+       }
+       macLength = mac.length;
+       serr = hmac->final(hmacCtx, mac.data, &macLength);
+       if(serr) {
+               goto fail;
+       }
+       mac.length = macLength;
+fail:
+       return serr;
+}
+       
+/*
+ * On input, the following are valid:
+ *             MasterSecret[48]
+ *             ClientHello.random[32]
+ *      ServerHello.random[32]
+ *
+ *      key_block = PRF(SecurityParameters.master_secret,
+ *                         "key expansion",
+ *                         SecurityParameters.server_random +
+ *                         SecurityParameters.client_random);
+ */
+#define GKM_SEED_LEN   (PLS_KEY_EXPAND_LEN + (2 * SSL_CLIENT_SRVR_RAND_SIZE))
+
+OSStatus tls1GenerateKeyMaterial (
+       SSLBuffer key,                                  // caller mallocs and specifies length of
+                                                                       //   required key material here
+       SSLContext *ctx)
+{
+       unsigned char seedBuf[GKM_SEED_LEN];
+       OSStatus serr;
+       
+       /* use optimized label-less PRF */
+       memmove(seedBuf, PLS_KEY_EXPAND, PLS_KEY_EXPAND_LEN);
+       memmove(seedBuf + PLS_KEY_EXPAND_LEN, ctx->serverRandom, 
+               SSL_CLIENT_SRVR_RAND_SIZE);
+       memmove(seedBuf + PLS_KEY_EXPAND_LEN + SSL_CLIENT_SRVR_RAND_SIZE, 
+               ctx->clientRandom, SSL_CLIENT_SRVR_RAND_SIZE);
+       serr = SSLInternal_PRF(ctx,
+               ctx->masterSecret,
+               SSL_MASTER_SECRET_SIZE,
+               NULL,                                           // no label
+               0,
+               seedBuf,
+               GKM_SEED_LEN,
+               key.data,                                       // destination
+               key.length);
+       tlsDump("key expansion", key.data, key.length);
+       return serr;
+}
+
+/*
+ *     final_client_write_key =
+ *                     PRF(SecurityParameters.client_write_key,
+ *                                 "client write key",
+ *                                 SecurityParameters.client_random +
+ *                                 SecurityParameters.server_random);
+ *     final_server_write_key =
+ *             PRF(SecurityParameters.server_write_key,
+ *                                 "server write key",
+ *                                 SecurityParameters.client_random +
+ *                                 SecurityParameters.server_random);
+ *
+ *     iv_block = PRF("", "IV block", SecurityParameters.client_random +
+ *                      SecurityParameters.server_random);
+ *
+ *        iv_block is broken up into:
+ *
+ *                     client_write_IV[SecurityParameters.IV_size]
+ *             server_write_IV[SecurityParameters.IV_size]
+ */    
+OSStatus tls1GenerateExportKeyAndIv (
+       SSLContext *ctx,                                // clientRandom, serverRandom valid
+       const SSLBuffer clientWriteKey,
+       const SSLBuffer serverWriteKey,
+       SSLBuffer finalClientWriteKey,  // RETURNED, mallocd by caller
+       SSLBuffer finalServerWriteKey,  // RETURNED, mallocd by caller
+       SSLBuffer finalClientIV,                // RETURNED, mallocd by caller
+       SSLBuffer finalServerIV)                // RETURNED, mallocd by caller
+{
+       unsigned char randBuf[2 * SSL_CLIENT_SRVR_RAND_SIZE];
+       OSStatus serr;
+       unsigned char *ivBlock;
+       char *nullKey = "";
+       
+       /* all three PRF calls use the same seed */
+       memmove(randBuf, ctx->clientRandom, SSL_CLIENT_SRVR_RAND_SIZE);
+       memmove(randBuf + SSL_CLIENT_SRVR_RAND_SIZE, 
+               ctx->serverRandom, SSL_CLIENT_SRVR_RAND_SIZE);
+               
+       serr = SSLInternal_PRF(ctx,
+               clientWriteKey.data,
+               clientWriteKey.length,
+               (const unsigned char *)PLS_EXPORT_CLIENT_WRITE,
+               PLS_EXPORT_CLIENT_WRITE_LEN,
+               randBuf,
+               2 * SSL_CLIENT_SRVR_RAND_SIZE,
+               finalClientWriteKey.data,               // destination
+               finalClientWriteKey.length);
+       if(serr) {
+               return serr;
+       }
+       serr = SSLInternal_PRF(ctx,
+               serverWriteKey.data,
+               serverWriteKey.length,
+               (const unsigned char *)PLS_EXPORT_SERVER_WRITE,
+               PLS_EXPORT_SERVER_WRITE_LEN,
+               randBuf,
+               2 * SSL_CLIENT_SRVR_RAND_SIZE,
+               finalServerWriteKey.data,               // destination
+               finalServerWriteKey.length);
+       if(serr) {
+               return serr;
+       }
+       if((finalClientIV.length == 0) && (finalServerIV.length == 0)) {
+               /* skip remainder as optimization */
+               return noErr;
+       }
+       ivBlock = (unsigned char *)sslMalloc(finalClientIV.length + finalServerIV.length);
+       if(ivBlock == NULL) {
+               return memFullErr;
+       }
+       serr = SSLInternal_PRF(ctx,
+               (const unsigned char *)nullKey,
+               0,
+               (const unsigned char *)PLS_EXPORT_IV_BLOCK,
+               PLS_EXPORT_IV_BLOCK_LEN,
+               randBuf,
+               2 * SSL_CLIENT_SRVR_RAND_SIZE,
+               ivBlock,                                        // destination
+               finalClientIV.length + finalServerIV.length);
+       if(serr) {
+               goto done;
+       }
+       memmove(finalClientIV.data, ivBlock, finalClientIV.length);
+       memmove(finalServerIV.data, ivBlock + finalClientIV.length, finalServerIV.length);
+done:
+       sslFree(ivBlock);
+       return serr;
+}
+
+/*
+ * On entry: clientRandom, serverRandom, preMasterSecret valid
+ * On return: masterSecret valid
+ *
+ * master_secret = PRF(pre_master_secret, "master secret",
+ *                                             ClientHello.random + ServerHello.random)
+ *      [0..47];
+ */
+OSStatus tls1GenerateMasterSecret (
+       SSLContext *ctx)
+{
+       unsigned char randBuf[2 * SSL_CLIENT_SRVR_RAND_SIZE];
+       OSStatus serr;
+       
+       memmove(randBuf, ctx->clientRandom, SSL_CLIENT_SRVR_RAND_SIZE);
+       memmove(randBuf + SSL_CLIENT_SRVR_RAND_SIZE, 
+               ctx->serverRandom, SSL_CLIENT_SRVR_RAND_SIZE);
+       serr = SSLInternal_PRF(ctx,
+               ctx->preMasterSecret.data,
+               ctx->preMasterSecret.length,
+               (const unsigned char *)PLS_MASTER_SECRET,
+               PLS_MASTER_SECRET_LEN,
+               randBuf,
+               2 * SSL_CLIENT_SRVR_RAND_SIZE,
+               ctx->masterSecret,              // destination
+               SSL_MASTER_SECRET_SIZE);
+       tlsDump("master secret", ctx->masterSecret, SSL_MASTER_SECRET_SIZE);
+       return serr;
+}
+       
+/*
+ * Given digests contexts representing the running total of all handshake messages,
+ * calculate mac for "finished" message. 
+ *
+ *                     verify_data = 12 bytes = 
+ *                             PRF(master_secret, finished_label, MD5(handshake_messages) +
+ *                                     SHA-1(handshake_messages)) [0..11];
+ */
+OSStatus tls1ComputeFinishedMac (
+       SSLContext *ctx,
+       SSLBuffer finished,             // output - mallocd by caller 
+       SSLBuffer shaMsgState,          // clone of running digest of all handshake msgs
+       SSLBuffer md5MsgState,          // ditto
+       Boolean isServer)
+{
+       unsigned char digests[SSL_MD5_DIGEST_LEN + SSL_SHA1_DIGEST_LEN];
+       SSLBuffer digBuf;
+       char *finLabel;
+       unsigned finLabelLen;
+       OSStatus serr;
+       
+       if(isServer) {
+               finLabel = PLS_SERVER_FINISH;
+               finLabelLen = PLS_SERVER_FINISH_LEN;
+       }
+       else {
+               finLabel = PLS_CLIENT_FINISH;
+               finLabelLen = PLS_CLIENT_FINISH_LEN;
+       }
+
+       /* concatenate two digest results */
+       digBuf.data = digests;
+       digBuf.length = SSL_MD5_DIGEST_LEN;
+       serr = SSLHashMD5.final(md5MsgState, digBuf);
+       if(serr) {
+               return serr;
+       }
+       digBuf.data += SSL_MD5_DIGEST_LEN;
+       digBuf.length = SSL_SHA1_DIGEST_LEN;
+       serr = SSLHashSHA1.final(shaMsgState, digBuf);
+       if(serr) {
+               return serr;
+       }
+       return SSLInternal_PRF(ctx,
+               ctx->masterSecret,
+               SSL_MASTER_SECRET_SIZE,
+               (const unsigned char *)finLabel,
+               finLabelLen,
+               digests,
+               SSL_MD5_DIGEST_LEN + SSL_SHA1_DIGEST_LEN,
+               finished.data,                          // destination
+               finished.length);
+}
+
+/*
+ * This one is trivial. 
+ *
+ * mac := MD5(handshake_messages) + SHA(handshake_messages);
+ *
+ * I don't know why this one doesn't use an HMAC or the master secret (as SSLv3
+ * does).
+ */
+OSStatus tls1ComputeCertVfyMac (
+       SSLContext *ctx,
+       SSLBuffer finished,             // output - mallocd by caller 
+       SSLBuffer shaMsgState,          // clone of running digest of all handshake msgs
+       SSLBuffer md5MsgState)          // ditto
+{
+       SSLBuffer digBuf;
+       OSStatus serr;
+       
+       assert(finished.length == (SSL_MD5_DIGEST_LEN + SSL_SHA1_DIGEST_LEN));
+       digBuf.data = finished.data;
+       digBuf.length = SSL_MD5_DIGEST_LEN;
+       serr = SSLHashMD5.final(md5MsgState, digBuf);
+       if(serr) {
+               return serr;
+       }
+       digBuf.data = finished.data + SSL_MD5_DIGEST_LEN;
+       digBuf.length = SSL_SHA1_DIGEST_LEN;
+       return SSLHashSHA1.final(shaMsgState, digBuf);
+}
+
+const SslTlsCallouts Tls1Callouts = {
+       tls1DecryptRecord,
+       ssl3WriteRecord,
+       tls1InitMac,
+       tls1FreeMac,
+       tls1ComputeMac,
+       tls1GenerateKeyMaterial,
+       tls1GenerateExportKeyAndIv,
+       tls1GenerateMasterSecret,
+       tls1ComputeFinishedMac,
+       tls1ComputeCertVfyMac
+};