/* * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. * * The contents of this file constitute Original Code as defined in and are * subject to the Apple Public Source License Version 1.2 (the 'License'). * You may not use this file except in compliance with the License. Please obtain * a copy of the License at http://www.apple.com/publicsource and read it before * using this file. * * This Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the * specific language governing rights and limitations under the License. */ /* File: ssl2Record.cpp Contains: Record encrypting/decrypting/MACing for SSL 2 Written by: Doug Mitchell Copyright: (c) 1999 by Apple Computer, Inc., all rights reserved. */ #include "ssl2.h" #include "sslRecord.h" #include "sslMemory.h" #include "sslContext.h" #include "sslAlertMessage.h" #include "sslDebug.h" #include "sslUtils.h" #include "sslDigests.h" #include static OSStatus SSL2DecryptRecord( SSLBuffer &payload, SSLContext *ctx); static OSStatus SSL2VerifyMAC( SSLBuffer &content, UInt8 *compareMAC, SSLContext *ctx); static OSStatus SSL2CalculateMAC( SSLBuffer &secret, SSLBuffer &content, UInt32 seqNo, const HashReference &hash, SSLBuffer &mac, SSLContext *ctx); OSStatus SSL2ReadRecord(SSLRecord &rec, SSLContext *ctx) { OSStatus err; UInt32 len, contentLen; int padding, headerSize; UInt8 *charPtr; SSLBuffer readData, cipherFragment; switch (ctx->negProtocolVersion) { case SSL_Version_Undetermined: case SSL_Version_2_0: break; case SSL_Version_3_0: /* We've negotiated a 3.0 session; * we can send an alert */ case TLS_Version_1_0: SSLFatalSessionAlert(SSL_AlertUnexpectedMsg, ctx); return errSSLProtocol; default: sslErrorLog("bad protocolVersion in ctx->protocolVersion"); return errSSLInternal; } if (!ctx->partialReadBuffer.data || ctx->partialReadBuffer.length < 3) { if (ctx->partialReadBuffer.data) if ((err = SSLFreeBuffer(ctx->partialReadBuffer, ctx)) != 0) { SSL2SendError(SSL2_ErrNoCipher, ctx); return err; } if ((err = SSLAllocBuffer(ctx->partialReadBuffer, DEFAULT_BUFFER_SIZE, ctx)) != 0) { SSL2SendError(SSL2_ErrNoCipher, ctx); return err; } } if (ctx->amountRead < 3) { readData.length = 3 - ctx->amountRead; readData.data = ctx->partialReadBuffer.data + ctx->amountRead; len = readData.length; err = sslIoRead(readData, &len, ctx); if(err != 0) { if (err == errSSLWouldBlock) ctx->amountRead += len; if (err == ioErr && ctx->amountRead == 0) /* If the session closes on a record boundary, it's graceful */ err = errSSLClosedGraceful; return err; } ctx->amountRead += len; } rec.contentType = SSL_RecordTypeV2_0; rec.protocolVersion = SSL_Version_2_0; charPtr = ctx->partialReadBuffer.data; if (((*charPtr) & 0x80) != 0) /* High bit on -> specifies 2-byte header */ { headerSize = 2; contentLen = ((charPtr[0] & 0x7F) << 8) | charPtr[1]; padding = 0; } else if (((*charPtr) & 0x40) != 0) /* Bit 6 on -> specifies security escape */ { return errSSLProtocol; /* No security escapes are defined */ } else /* 3-byte header */ { headerSize = 3; contentLen = ((charPtr[0] & 0x3F) << 8) | charPtr[1]; padding = charPtr[2]; } /* * FIXME - what's the max record size? * and why doesn't SSLReadRecord parse the 2 or 3 byte header? * Note: I see contentLen of 0 coming back from www.cduniverse.com when * it's only been given SSL_RSA_EXPORT_WITH_DES40_CBC_SHA. */ if((contentLen == 0) || (contentLen > 0xffff)) { return errSSLProtocol; } charPtr += headerSize; if (ctx->partialReadBuffer.length < headerSize + contentLen) { if ((err = SSLReallocBuffer(ctx->partialReadBuffer, 5 + contentLen, ctx)) != 0) return err; } if (ctx->amountRead < headerSize + contentLen) { readData.length = headerSize + contentLen - ctx->amountRead; readData.data = ctx->partialReadBuffer.data + ctx->amountRead; len = readData.length; err = sslIoRead(readData, &len, ctx); if(err != 0) { if (err == errSSLWouldBlock) ctx->amountRead += len; return err; } ctx->amountRead += len; } cipherFragment.data = ctx->partialReadBuffer.data + headerSize; cipherFragment.length = contentLen; if ((err = SSL2DecryptRecord(cipherFragment, ctx)) != 0) return err; cipherFragment.length -= padding; /* Remove padding; MAC was removed * by SSL2DecryptRecord */ IncrementUInt64(&ctx->readCipher.sequenceNum); /* Allocate a buffer to return the plaintext in and return it */ if ((err = SSLAllocBuffer(rec.contents, cipherFragment.length, ctx)) != 0) return err; memcpy(rec.contents.data, cipherFragment.data, cipherFragment.length); ctx->amountRead = 0; /* We've used all the data in the cache */ return noErr; } OSStatus SSL2WriteRecord(SSLRecord &rec, SSLContext *ctx) { OSStatus err; int padding = 0, i, headerSize; WaitingRecord *out, *queue; SSLBuffer buf, content, payload, secret, mac; UInt8 *charPtr; UInt16 payloadSize, blockSize; 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; payloadSize = (UInt16) (rec.contents.length + ctx->writeCipher.macRef->hash->digestSize); blockSize = ctx->writeCipher.symCipher->blockSize; if (blockSize > 0) { padding = blockSize - (payloadSize % blockSize); if (padding == blockSize) padding = 0; payloadSize += padding; headerSize = 3; } else { padding = 0; headerSize = 2; } out->data.data = 0; if ((err = SSLAllocBuffer(out->data, headerSize + payloadSize, ctx)) != 0) goto fail; charPtr = out->data.data; if (headerSize == 2) charPtr = SSLEncodeInt(charPtr, payloadSize | 0x8000, 2); else { charPtr = SSLEncodeInt(charPtr, payloadSize, 2); *charPtr++ = padding; } payload.data = charPtr; payload.length = payloadSize; mac.data = charPtr; mac.length = ctx->writeCipher.macRef->hash->digestSize; charPtr += mac.length; content.data = charPtr; content.length = rec.contents.length + padding; memcpy(charPtr, rec.contents.data, rec.contents.length); charPtr += rec.contents.length; i = padding; while (i--) *charPtr++ = padding; assert(charPtr == out->data.data + out->data.length); secret.data = ctx->writeCipher.macSecret; secret.length = ctx->writeCipher.symCipher->keySize; if (mac.length > 0) if ((err = SSL2CalculateMAC(secret, content, ctx->writeCipher.sequenceNum.low, *ctx->writeCipher.macRef->hash, mac, ctx)) != 0) goto fail; 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, 0); buf.data = (UInt8*)out; buf.length = sizeof(WaitingRecord); SSLFreeBuffer(buf, ctx); return err; } static OSStatus SSL2DecryptRecord(SSLBuffer &payload, SSLContext *ctx) { OSStatus err; SSLBuffer content; if (ctx->readCipher.symCipher->blockSize > 0) if (payload.length % ctx->readCipher.symCipher->blockSize != 0) return errSSLProtocol; /* Decrypt in place */ if ((err = ctx->readCipher.symCipher->decrypt(payload, payload, &ctx->readCipher, ctx)) != 0) return err; if (ctx->readCipher.macRef->hash->digestSize > 0) /* Optimize away MAC for null case */ { content.data = payload.data + ctx->readCipher.macRef->hash->digestSize; /* Data is after MAC */ content.length = payload.length - ctx->readCipher.macRef->hash->digestSize; if ((err = SSL2VerifyMAC(content, payload.data, ctx)) != 0) return err; /* Adjust payload to remove MAC; caller is still responsible * for removing padding [if any] */ payload = content; } return noErr; } #define IGNORE_MAC_FAILURE 0 static OSStatus SSL2VerifyMAC(SSLBuffer &content, UInt8 *compareMAC, SSLContext *ctx) { OSStatus err; UInt8 calculatedMAC[SSL_MAX_DIGEST_LEN]; SSLBuffer secret, mac; secret.data = ctx->readCipher.macSecret; secret.length = ctx->readCipher.symCipher->keySize; mac.data = calculatedMAC; mac.length = ctx->readCipher.macRef->hash->digestSize; if ((err = SSL2CalculateMAC(secret, content, ctx->readCipher.sequenceNum.low, *ctx->readCipher.macRef->hash, mac, ctx)) != 0) return err; if (memcmp(mac.data, compareMAC, mac.length) != 0) { #if IGNORE_MAC_FAILURE sslErrorLog("SSL2VerifyMAC: Mac verify failure\n"); return noErr; #else sslErrorLog("SSL2VerifyMAC: Mac verify failure\n"); return errSSLProtocol; #endif } return noErr; } #define LOG_MAC_DATA 0 #if LOG_MAC_DATA static void logMacData( char *field, SSLBuffer *data) { int i; printf("%s: ", field); for(i=0; ilength; i++) { printf("%02X", data->data[i]); if((i % 4) == 3) { printf(" "); } } printf("\n"); } #else /* LOG_MAC_DATA */ #define logMacData(f, d) #endif /* LOG_MAC_DATA */ /* For SSL 2, the MAC is hash ( secret || content || sequence# ) * where secret is the decryption key for the message, content is * the record data plus any padding used to round out the record * size to an even multiple of the block size and sequence# is * a monotonically increasing 32-bit unsigned integer. */ static OSStatus SSL2CalculateMAC( SSLBuffer &secret, SSLBuffer &content, UInt32 seqNo, const HashReference &hash, SSLBuffer &mac, SSLContext *ctx) { OSStatus err; UInt8 sequenceNum[4]; SSLBuffer seqData, hashContext; SSLEncodeInt(sequenceNum, seqNo, 4); seqData.data = sequenceNum; seqData.length = 4; hashContext.data = 0; if ((err = ReadyHash(hash, hashContext, ctx)) != 0) return err; if ((err = hash.update(hashContext, secret)) != 0) goto fail; if ((err = hash.update(hashContext, content)) != 0) goto fail; if ((err = hash.update(hashContext, seqData)) != 0) goto fail; if ((err = hash.final(hashContext, mac)) != 0) goto fail; logMacData("secret ", &secret); logMacData("seqData", &seqData); logMacData("mac ", &mac); err = noErr; fail: SSLFreeBuffer(hashContext, ctx); return err; } OSStatus SSL2SendError(SSL2ErrorCode error, SSLContext *ctx) { OSStatus err; SSLRecord rec; UInt8 errorData[3]; rec.contentType = SSL_RecordTypeV2_0; rec.protocolVersion = SSL_Version_2_0; rec.contents.data = errorData; rec.contents.length = 3; errorData[0] = SSL2_MsgError; SSLEncodeInt(errorData + 1, error, 2); err = SSL2WriteRecord(rec, ctx); return err; }