/* * 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 #include #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= 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; ireadCipher.symCipher->blockSize > 0) && ((payload->length % ctx->readCipher.symCipher->blockSize) != 0)) { SSLFatalSessionAlert(SSL_AlertRecordOverflow, ctx); return errSSLRecordOverflow; } /* Decrypt in place */ if ((err = ctx->readCipher.symCipher->decrypt(*payload, *payload, &ctx->readCipher, ctx)) != 0) { SSLFatalSessionAlert(SSL_AlertDecryptError, ctx); return errSSLDecryptionFail; } /* 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_AlertDecodeError, ctx); sslErrorLog("tls1DecryptRecord: bad padding length (%d)\n", (unsigned)payload->data[payload->length - 1]); return errSSLDecryptionFail; } padChars = payload->data + payload->length - padSize; while(padChars < (payload->data + payload->length)) { if(*padChars++ != padSize) { SSLFatalSessionAlert(SSL_AlertDecodeError, ctx); sslErrorLog("tls1DecryptRecord: bad padding value\n"); return errSSLDecryptionFail; } } /* 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 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 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 };