/* * 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: ssl3Callouts.cpp Contains: SSLv3-specific routines for SslTlsCallouts. Written by: Doug Mitchell */ #include "sslMemory.h" #include "tls_ssl.h" #include "sslUtils.h" #include "sslDigests.h" #include "ssl2.h" #include "sslDebug.h" #include "sslAlertMessage.h" #include #include /* * ssl3WriteRecord does not send alerts on failure, out of the assumption/fear * that this might result in a loop (since sending an alert causes ssl3WriteRecord * to be called). * * As far as I can tell, we can use this same routine for SSLv3 and TLSv1, as long * as we're not trying to use the "variable length padding" feature of TLSv1. * OpenSSL doesn't use that feature; for now, neither will we. Thus this routine * is used for the SslTlsCallouts.writeRecord function for both protocols. */ OSStatus ssl3WriteRecord( SSLRecord rec, SSLContext *ctx) { OSStatus err; int padding = 0, i; WaitingRecord *out, *queue; SSLBuffer buf, payload, mac; UInt8 *charPtr; UInt16 payloadSize,blockSize; switch(rec.protocolVersion) { case SSL_Version_2_0: return SSL2WriteRecord(rec, ctx); case SSL_Version_3_0: case TLS_Version_1_0: break; default: assert(0); return errSSLInternal; } assert(rec.contents.length <= 16384); out = 0; /* Allocate a WaitingRecord to store our ready-to-send record in */ if ((err = SSLAllocBuffer(buf, sizeof(WaitingRecord), ctx)) != 0) return err; out = (WaitingRecord*)buf.data; out->next = 0; out->sent = 0; /* Allocate enough room for the transmitted record, which will be: * 5 bytes of header + * encrypted contents + * macLength + * padding [block ciphers only] + * padding length field (1 byte) [block ciphers only] */ payloadSize = (UInt16) (rec.contents.length + ctx->writeCipher.macRef->hash->digestSize); blockSize = ctx->writeCipher.symCipher->blockSize; if (blockSize > 0) { padding = blockSize - (payloadSize % blockSize) - 1; payloadSize += padding + 1; } out->data.data = 0; if ((err = SSLAllocBuffer(out->data, 5 + payloadSize, ctx)) != 0) goto fail; charPtr = out->data.data; *(charPtr++) = rec.contentType; charPtr = SSLEncodeInt(charPtr, rec.protocolVersion, 2); charPtr = SSLEncodeInt(charPtr, payloadSize, 2); /* Copy the contents into the output buffer */ memcpy(charPtr, rec.contents.data, rec.contents.length); payload.data = charPtr; payload.length = rec.contents.length; charPtr += rec.contents.length; /* MAC immediately follows data */ mac.data = charPtr; mac.length = ctx->writeCipher.macRef->hash->digestSize; charPtr += mac.length; /* MAC the data */ if (mac.length > 0) /* Optimize away null case */ { assert(ctx->sslTslCalls != NULL); if ((err = ctx->sslTslCalls->computeMac(rec.contentType, payload, mac, &ctx->writeCipher, ctx->writeCipher.sequenceNum, ctx)) != 0) goto fail; } /* Update payload to reflect encrypted data: contents, mac & padding */ payload.length = payloadSize; /* Fill in the padding bytes & padding length field with the padding value; the * protocol only requires the last byte, * but filling them all in avoids leaking data */ if (ctx->writeCipher.symCipher->blockSize > 0) for (i = 1; i <= padding + 1; ++i) payload.data[payload.length - i] = padding; /* Encrypt the data */ if ((err = ctx->writeCipher.symCipher->encrypt(payload, payload, &ctx->writeCipher, ctx)) != 0) goto fail; /* Enqueue the record to be written from the idle loop */ if (ctx->recordWriteQueue == 0) ctx->recordWriteQueue = out; else { queue = ctx->recordWriteQueue; while (queue->next != 0) queue = queue->next; queue->next = out; } /* Increment the sequence number */ IncrementUInt64(&ctx->writeCipher.sequenceNum); return noErr; fail: /* * Only for if we fail between when the WaitingRecord is allocated and when * it is queued */ SSLFreeBuffer(out->data, ctx); buf.data = (UInt8*)out; buf.length = sizeof(WaitingRecord); SSLFreeBuffer(buf, ctx); return err; } static OSStatus ssl3DecryptRecord( 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_AlertDecryptionFail, 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) { /* padding can't be equal to or more than a block */ if (payload->data[payload->length - 1] >= ctx->readCipher.symCipher->blockSize) { SSLFatalSessionAlert(SSL_AlertDecryptionFail, ctx); sslErrorLog("DecryptSSLRecord: bad padding length (%d)\n", (unsigned)payload->data[payload->length - 1]); return errSSLDecryptionFail; } content.length -= 1 + payload->data[payload->length - 1]; /* Remove block size padding */ } /* 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 errSSLBadRecordMac; } *payload = content; /* Modify payload buffer to indicate content length */ return noErr; } /* initialize a per-CipherContext HashHmacContext for use in MACing each record */ static OSStatus ssl3InitMac ( CipherContext *cipherCtx, // macRef, macSecret valid on entry // macCtx valid on return SSLContext *ctx) { const HashReference *hash; SSLBuffer *hashCtx; OSStatus serr; assert(cipherCtx->macRef != NULL); hash = cipherCtx->macRef->hash; assert(hash != NULL); hashCtx = &cipherCtx->macCtx.hashCtx; if(hashCtx->data != NULL) { SSLFreeBuffer(*hashCtx, ctx); } serr = SSLAllocBuffer(*hashCtx, hash->contextSize, ctx); if(serr) { return serr; } return noErr; } static OSStatus ssl3FreeMac ( CipherContext *cipherCtx) { SSLBuffer *hashCtx; assert(cipherCtx != NULL); /* this can be called on a completely zeroed out CipherContext... */ if(cipherCtx->macRef == NULL) { return noErr; } hashCtx = &cipherCtx->macCtx.hashCtx; if(hashCtx->data != NULL) { sslFree(hashCtx->data); hashCtx->data = NULL; } hashCtx->length = 0; return noErr; } static OSStatus ssl3ComputeMac ( UInt8 type, SSLBuffer data, SSLBuffer mac, // caller mallocs data CipherContext *cipherCtx, // assumes macCtx, macRef sslUint64 seqNo, SSLContext *ctx) { OSStatus err; UInt8 innerDigestData[SSL_MAX_DIGEST_LEN]; UInt8 scratchData[11], *charPtr; SSLBuffer digest, digestCtx, scratch; SSLBuffer secret; const HashReference *hash; assert(cipherCtx != NULL); assert(cipherCtx->macRef != NULL); hash = cipherCtx->macRef->hash; assert(hash != NULL); assert(hash->macPadSize <= MAX_MAC_PADDING); assert(hash->digestSize <= SSL_MAX_DIGEST_LEN); digestCtx = cipherCtx->macCtx.hashCtx; // may be NULL, for null cipher secret.data = cipherCtx->macSecret; secret.length = hash->digestSize; /* init'd early in SSLNewContext() */ assert(SSLMACPad1[0] == 0x36 && SSLMACPad2[0] == 0x5C); /* * MAC = hash( MAC_write_secret + pad_2 + * hash( MAC_write_secret + pad_1 + seq_num + type + * length + content ) * ) */ if ((err = hash->init(digestCtx, ctx)) != 0) goto exit; if ((err = hash->update(digestCtx, secret)) != 0) /* MAC secret */ goto exit; scratch.data = (UInt8 *)SSLMACPad1; scratch.length = hash->macPadSize; if ((err = hash->update(digestCtx, scratch)) != 0) /* pad1 */ goto exit; charPtr = scratchData; charPtr = SSLEncodeUInt64(charPtr, seqNo); *charPtr++ = type; charPtr = SSLEncodeInt(charPtr, data.length, 2); scratch.data = scratchData; scratch.length = 11; assert(charPtr = scratchData+11); if ((err = hash->update(digestCtx, scratch)) != 0) /* sequenceNo, type & length */ goto exit; if ((err = hash->update(digestCtx, data)) != 0) /* content */ goto exit; digest.data = innerDigestData; digest.length = hash->digestSize; if ((err = hash->final(digestCtx, digest)) != 0) /* figure inner digest */ goto exit; if ((err = hash->init(digestCtx, ctx)) != 0) goto exit; if ((err = hash->update(digestCtx, secret)) != 0) /* MAC secret */ goto exit; scratch.data = (UInt8 *)SSLMACPad2; scratch.length = hash->macPadSize; if ((err = hash->update(digestCtx, scratch)) != 0) /* pad2 */ goto exit; if ((err = hash->update(digestCtx, digest)) != 0) /* inner digest */ goto exit; if ((err = hash->final(digestCtx, mac)) != 0) /* figure the mac */ goto exit; err = noErr; /* redundant, I know */ exit: return err; } #define LOG_GEN_KEY 0 /* * On input, the following are valid: * MasterSecret[48] * ClientHello.random[32] * ServerHello.random[32] * * key_block = * MD5(master_secret + SHA(`A' + master_secret + * ServerHello.random + * ClientHello.random)) + * MD5(master_secret + SHA(`BB' + master_secret + * ServerHello.random + * ClientHello.random)) + * MD5(master_secret + SHA(`CCC' + master_secret + * ServerHello.random + * ClientHello.random)) + [...]; */ static OSStatus ssl3GenerateKeyMaterial ( SSLBuffer key, // caller mallocs and specifies length of // required key material here SSLContext *ctx) { OSStatus err; UInt8 leaderData[10]; /* Max of 10 hashes * (* 16 bytes/hash = 160 bytes of key) */ UInt8 shaHashData[20], md5HashData[16]; SSLBuffer shaContext, md5Context; UInt8 *keyProgress; int i,j,remaining, satisfied; SSLBuffer leader, masterSecret, serverRandom, clientRandom, shaHash, md5Hash; #if LOG_GEN_KEY printf("GenerateKey: master "); for(i=0; imasterSecret[i]); } printf("\n"); #endif assert(key.length <= 16 * sizeof(leaderData)); leader.data = leaderData; masterSecret.data = ctx->masterSecret; masterSecret.length = SSL_MASTER_SECRET_SIZE; serverRandom.data = ctx->serverRandom; serverRandom.length = SSL_CLIENT_SRVR_RAND_SIZE; clientRandom.data = ctx->clientRandom; clientRandom.length = SSL_CLIENT_SRVR_RAND_SIZE; shaHash.data = shaHashData; shaHash.length = 20; md5Hash.data = md5HashData; md5Hash.length = 16; md5Context.data = 0; shaContext.data = 0; if ((err = ReadyHash(SSLHashMD5, md5Context, ctx)) != 0) goto fail; if ((err = ReadyHash(SSLHashSHA1, shaContext, ctx)) != 0) goto fail; keyProgress = key.data; remaining = key.length; for (i = 0; remaining > 0; ++i) { for (j = 0; j <= i; j++) leaderData[j] = 0x41 + i; /* 'A', 'BB', 'CCC', etc. */ leader.length = i+1; if ((err = SSLHashSHA1.update(shaContext, leader)) != 0) goto fail; if ((err = SSLHashSHA1.update(shaContext, masterSecret)) != 0) goto fail; if ((err = SSLHashSHA1.update(shaContext, serverRandom)) != 0) goto fail; if ((err = SSLHashSHA1.update(shaContext, clientRandom)) != 0) goto fail; if ((err = SSLHashSHA1.final(shaContext, shaHash)) != 0) goto fail; if ((err = SSLHashMD5.update(md5Context, masterSecret)) != 0) goto fail; if ((err = SSLHashMD5.update(md5Context, shaHash)) != 0) goto fail; if ((err = SSLHashMD5.final(md5Context, md5Hash)) != 0) goto fail; satisfied = 16; if (remaining < 16) satisfied = remaining; memcpy(keyProgress, md5HashData, satisfied); remaining -= satisfied; keyProgress += satisfied; if(remaining > 0) { /* at top of loop, this was done in ReadyHash() */ if ((err = SSLHashMD5.init(md5Context, ctx)) != 0) goto fail; if ((err = SSLHashSHA1.init(shaContext, ctx)) != 0) goto fail; } } assert(remaining == 0 && keyProgress == (key.data + key.length)); err = noErr; fail: SSLFreeBuffer(md5Context, ctx); SSLFreeBuffer(shaContext, ctx); #if LOG_GEN_KEY printf("GenerateKey: DONE\n"); #endif return err; } static OSStatus ssl3GenerateExportKeyAndIv ( 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 { OSStatus err; SSLBuffer hashCtx, serverRandom, clientRandom; /* random blobs are 32 bytes */ serverRandom.data = ctx->serverRandom; serverRandom.length = SSL_CLIENT_SRVR_RAND_SIZE; clientRandom.data = ctx->clientRandom; clientRandom.length = SSL_CLIENT_SRVR_RAND_SIZE; if ((err = SSLAllocBuffer(hashCtx, SSLHashMD5.contextSize, ctx)) != 0) return err; /* client write key */ if ((err = SSLHashMD5.init(hashCtx, ctx)) != 0) goto fail; if ((err = SSLHashMD5.update(hashCtx, clientWriteKey)) != 0) goto fail; if ((err = SSLHashMD5.update(hashCtx, clientRandom)) != 0) goto fail; if ((err = SSLHashMD5.update(hashCtx, serverRandom)) != 0) goto fail; finalClientWriteKey.length = 16; if ((err = SSLHashMD5.final(hashCtx, finalClientWriteKey)) != 0) goto fail; /* optional client IV */ if (ctx->selectedCipherSpec->cipher->ivSize > 0) { if ((err = SSLHashMD5.init(hashCtx, ctx)) != 0) goto fail; if ((err = SSLHashMD5.update(hashCtx, clientRandom)) != 0) goto fail; if ((err = SSLHashMD5.update(hashCtx, serverRandom)) != 0) goto fail; finalClientIV.length = 16; if ((err = SSLHashMD5.final(hashCtx, finalClientIV)) != 0) goto fail; } /* server write key */ if ((err = SSLHashMD5.init(hashCtx, ctx)) != 0) goto fail; if ((err = SSLHashMD5.update(hashCtx, serverWriteKey)) != 0) goto fail; if ((err = SSLHashMD5.update(hashCtx, serverRandom)) != 0) goto fail; if ((err = SSLHashMD5.update(hashCtx, clientRandom)) != 0) goto fail; finalServerWriteKey.length = 16; if ((err = SSLHashMD5.final(hashCtx, finalServerWriteKey)) != 0) goto fail; /* optional server IV */ if (ctx->selectedCipherSpec->cipher->ivSize > 0) { if ((err = SSLHashMD5.init(hashCtx, ctx)) != 0) goto fail; if ((err = SSLHashMD5.update(hashCtx, serverRandom)) != 0) goto fail; if ((err = SSLHashMD5.update(hashCtx, clientRandom)) != 0) goto fail; finalServerIV.length = 16; if ((err = SSLHashMD5.final(hashCtx, finalServerIV)) != 0) goto fail; } err = noErr; fail: SSLFreeBuffer(hashCtx, ctx); return err; } /* * On entry: clientRandom, serverRandom, preMasterSecret valid * On return: masterSecret valid */ static OSStatus ssl3GenerateMasterSecret ( SSLContext *ctx) { OSStatus err; SSLBuffer shaState, md5State, clientRandom, serverRandom, shaHash, md5Hash, leader; UInt8 *masterProgress, shaHashData[20], leaderData[3]; int i; md5State.data = shaState.data = 0; if ((err = SSLAllocBuffer(md5State, SSLHashMD5.contextSize, ctx)) != 0) goto fail; if ((err = SSLAllocBuffer(shaState, SSLHashSHA1.contextSize, ctx)) != 0) goto fail; clientRandom.data = ctx->clientRandom; clientRandom.length = SSL_CLIENT_SRVR_RAND_SIZE; serverRandom.data = ctx->serverRandom; serverRandom.length = SSL_CLIENT_SRVR_RAND_SIZE; shaHash.data = shaHashData; shaHash.length = 20; masterProgress = ctx->masterSecret; for (i = 1; i <= 3; i++) { if ((err = SSLHashMD5.init(md5State, ctx)) != 0) goto fail; if ((err = SSLHashSHA1.init(shaState, ctx)) != 0) goto fail; leaderData[0] = leaderData[1] = leaderData[2] = 0x40 + i; /* 'A', 'B', etc. */ leader.data = leaderData; leader.length = i; if ((err = SSLHashSHA1.update(shaState, leader)) != 0) goto fail; if ((err = SSLHashSHA1.update(shaState, ctx->preMasterSecret)) != 0) goto fail; if ((err = SSLHashSHA1.update(shaState, clientRandom)) != 0) goto fail; if ((err = SSLHashSHA1.update(shaState, serverRandom)) != 0) goto fail; if ((err = SSLHashSHA1.final(shaState, shaHash)) != 0) goto fail; if ((err = SSLHashMD5.update(md5State, ctx->preMasterSecret)) != 0) goto fail; if ((err = SSLHashMD5.update(md5State, shaHash)) != 0) goto fail; md5Hash.data = masterProgress; md5Hash.length = 16; if ((err = SSLHashMD5.final(md5State, md5Hash)) != 0) goto fail; masterProgress += 16; } err = noErr; fail: SSLFreeBuffer(shaState, ctx); SSLFreeBuffer(md5State, ctx); return err; } /* common routine to compute a Mac for finished message and cert verify message */ static OSStatus ssl3CalculateFinishedMessage( SSLContext *ctx, SSLBuffer finished, // mallocd by caller SSLBuffer shaMsgState, // running total SSLBuffer md5MsgState, // ditto UInt32 senderID) // optional, nonzero for finished message { OSStatus err; SSLBuffer hash, input; UInt8 sender[4], md5Inner[16], shaInner[20]; // assert(finished.length == 36); if (senderID != 0) { SSLEncodeInt(sender, senderID, 4); input.data = sender; input.length = 4; if ((err = SSLHashMD5.update(md5MsgState, input)) != 0) return err; if ((err = SSLHashSHA1.update(shaMsgState, input)) != 0) return err; } input.data = ctx->masterSecret; input.length = SSL_MASTER_SECRET_SIZE; if ((err = SSLHashMD5.update(md5MsgState, input)) != 0) return err; if ((err = SSLHashSHA1.update(shaMsgState, input)) != 0) return err; input.data = (UInt8 *)SSLMACPad1; input.length = SSLHashMD5.macPadSize; if ((err = SSLHashMD5.update(md5MsgState, input)) != 0) return err; input.length = SSLHashSHA1.macPadSize; if ((err = SSLHashSHA1.update(shaMsgState, input)) != 0) return err; hash.data = md5Inner; hash.length = 16; if ((err = SSLHashMD5.final(md5MsgState, hash)) != 0) return err; hash.data = shaInner; hash.length = 20; if ((err = SSLHashSHA1.final(shaMsgState, hash)) != 0) return err; if ((err = SSLHashMD5.init(md5MsgState, ctx)) != 0) return err; if ((err = SSLHashSHA1.init(shaMsgState, ctx)) != 0) return err; input.data = ctx->masterSecret; input.length = SSL_MASTER_SECRET_SIZE; if ((err = SSLHashMD5.update(md5MsgState, input)) != 0) return err; if ((err = SSLHashSHA1.update(shaMsgState, input)) != 0) return err; input.data = (UInt8 *)SSLMACPad2; input.length = SSLHashMD5.macPadSize; if ((err = SSLHashMD5.update(md5MsgState, input)) != 0) return err; input.length = SSLHashSHA1.macPadSize; if ((err = SSLHashSHA1.update(shaMsgState, input)) != 0) return err; input.data = md5Inner; input.length = 16; if ((err = SSLHashMD5.update(md5MsgState, input)) != 0) return err; hash.data = finished.data; hash.length = 16; if ((err = SSLHashMD5.final(md5MsgState, hash)) != 0) return err; input.data = shaInner; input.length = 20; if ((err = SSLHashSHA1.update(shaMsgState, input)) != 0) return err; hash.data = finished.data + 16; hash.length = 20; if ((err = SSLHashSHA1.final(shaMsgState, hash)) != 0) return err; return noErr; } static OSStatus ssl3ComputeFinishedMac ( SSLContext *ctx, SSLBuffer finished, // output - mallocd by caller SSLBuffer shaMsgState, // clone of running digest of all handshake msgs SSLBuffer md5MsgState, // ditto Boolean isServer) // refers to message, not us { return ssl3CalculateFinishedMessage(ctx, finished, shaMsgState, md5MsgState, isServer ? SSL_Finished_Sender_Server : SSL_Finished_Sender_Client); } static OSStatus ssl3ComputeCertVfyMac ( SSLContext *ctx, SSLBuffer finished, // output - mallocd by caller SSLBuffer shaMsgState, // clone of running digest of all handshake msgs SSLBuffer md5MsgState) // ditto { return ssl3CalculateFinishedMessage(ctx, finished, shaMsgState, md5MsgState, 0); } const SslTlsCallouts Ssl3Callouts = { ssl3DecryptRecord, ssl3WriteRecord, ssl3InitMac, ssl3FreeMac, ssl3ComputeMac, ssl3GenerateKeyMaterial, ssl3GenerateExportKeyAndIv, ssl3GenerateMasterSecret, ssl3ComputeFinishedMac, ssl3ComputeCertVfyMac };