--- /dev/null
+/*
+ * Copyright (c) 1999-2001,2005-2014 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@
+ */
+
+/*
+ * sslTransport.c - SSL transport layer
+ */
+
+#include "ssl.h"
+#include "sslMemory.h"
+#include "sslContext.h"
+#include "sslRecord.h"
+#include "sslDebug.h"
+#include "sslCipherSpecs.h"
+#include "sslUtils.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include <utilities/SecIOFormat.h>
+
+#ifndef NDEBUG
+static inline void sslIoTrace(
+ SSLContext *ctx,
+ const char *op,
+ size_t req,
+ size_t moved,
+ OSStatus stat)
+{
+ sslLogRecordIo("[%p] ===%s: req %4lu moved %4lu status %d",
+ ctx, op, req, moved, (int)stat);
+}
+#else
+#define sslIoTrace(ctx, op, req, moved, stat)
+#endif /* NDEBUG */
+
+extern int kSplitDefaultValue;
+
+static OSStatus SSLProcessProtocolMessage(SSLRecord *rec, SSLContext *ctx);
+static OSStatus SSLHandshakeProceed(SSLContext *ctx);
+//static OSStatus SSLInitConnection(SSLContext *ctx);
+
+OSStatus
+SSLWrite(
+ SSLContext *ctx,
+ const void * data,
+ size_t dataLength,
+ size_t *bytesWritten) /* RETURNED */
+{
+ OSStatus err;
+ SSLRecord rec;
+ size_t dataLen, processed;
+
+ sslLogRecordIo("[%p] SSLWrite top", ctx);
+ if((ctx == NULL) || (bytesWritten == NULL)) {
+ return errSecParam;
+ }
+ dataLen = dataLength;
+ processed = 0; /* Initialize in case we return with errSSLWouldBlock */
+ *bytesWritten = 0;
+
+ switch(ctx->state) {
+ case SSL_HdskStateGracefulClose:
+ err = errSSLClosedGraceful;
+ goto abort;
+ case SSL_HdskStateErrorClose:
+ err = errSSLClosedAbort;
+ goto abort;
+ case SSL_HdskStateReady:
+ break;
+ case SSL_HdskStateUninit:
+ /* not ready for I/O, and handshake not in progress */
+ sslIoTrace(ctx, "SSLWrite(1)", dataLength, 0, errSecBadReq);
+ return errSecBadReq;
+ default:
+ /* handshake in progress or done. Will call SSLHandshakeProceed below if necessary */
+ break;
+ }
+
+ /* First, we have to wait until the session is ready to send data,
+ so the encryption keys and such have been established. */
+ err = errSecSuccess;
+ while (!(ctx->writeCipher_ready))
+ { if ((err = SSLHandshakeProceed(ctx)) != 0)
+ goto exit;
+ }
+
+ /* Attempt to empty the write queue before queueing more data */
+ if ((err = SSLServiceWriteQueue(ctx)) != 0)
+ goto abort;
+
+ processed = 0;
+
+ /* Skip empty writes, fragmentation is done at the coreTLS layer */
+ if(dataLen) {
+ rec.contentType = SSL_RecordTypeAppData;
+ rec.protocolVersion = ctx->negProtocolVersion;
+ rec.contents.data = ((uint8_t *)data) + processed;
+ rec.contents.length = dataLen;
+ if ((err = SSLWriteRecord(rec, ctx)) != 0)
+ goto exit;
+ processed += rec.contents.length;
+ }
+
+ /* All the data has been advanced to the write queue */
+ *bytesWritten = processed;
+ if ((err = SSLServiceWriteQueue(ctx)) == 0) {
+ err = errSecSuccess;
+ }
+exit:
+ switch(err) {
+ case errSecSuccess:
+ case errSSLWouldBlock:
+ case errSSLUnexpectedRecord:
+ case errSSLServerAuthCompleted: /* == errSSLClientAuthCompleted */
+ case errSSLClientCertRequested:
+ case errSSLClosedGraceful:
+ break;
+ default:
+ sslErrorLog("SSLWrite: going to state errorClose due to err %d\n",
+ (int)err);
+ SSLChangeHdskState(ctx, SSL_HdskStateErrorClose);
+ break;
+ }
+abort:
+ sslIoTrace(ctx, "SSLWrite(2)", dataLength, *bytesWritten, err);
+ return err;
+}
+
+OSStatus
+SSLRead (
+ SSLContext *ctx,
+ void * data,
+ size_t dataLength,
+ size_t *processed) /* RETURNED */
+{
+ OSStatus err;
+ uint8_t *charPtr;
+ size_t bufSize, remaining, count;
+ SSLRecord rec;
+
+ sslLogRecordIo("[%p] SSLRead top (dataLength=%ld)", ctx, dataLength);
+ if((ctx == NULL) || (data == NULL) || (processed == NULL)) {
+ return errSecParam;
+ }
+ bufSize = dataLength;
+ *processed = 0; /* Initialize in case we return with errSSLWouldBlock */
+
+readRetry:
+ /* first handle cases in which we know we're finished */
+ switch(ctx->state) {
+ case SSL_HdskStateGracefulClose:
+ err = errSSLClosedGraceful;
+ goto abort;
+ case SSL_HdskStateErrorClose:
+ err = errSSLClosedAbort;
+ goto abort;
+ case SSL_HdskStateNoNotifyClose:
+ err = errSSLClosedNoNotify;
+ goto abort;
+ default:
+ break;
+ }
+
+ /* First, we have to wait until the session is ready to receive data,
+ so the encryption keys and such have been established. */
+ err = errSecSuccess;
+ while (ctx->readCipher_ready == 0) {
+ if ((err = SSLHandshakeProceed(ctx)) != 0) {
+ goto exit;
+ }
+ }
+
+ /* Attempt to service the write queue */
+ if ((err = SSLServiceWriteQueue(ctx)) != 0) {
+ if (err != errSSLWouldBlock) {
+ goto exit;
+ }
+ err = errSecSuccess; /* Write blocking shouldn't stop attempts to read */
+ }
+
+ remaining = bufSize;
+ charPtr = (uint8_t *)data;
+ if (ctx->receivedDataBuffer.data)
+ { count = ctx->receivedDataBuffer.length - ctx->receivedDataPos;
+ if (count > bufSize)
+ count = bufSize;
+ memcpy(data, ctx->receivedDataBuffer.data + ctx->receivedDataPos, count);
+ remaining -= count;
+ charPtr += count;
+ *processed += count;
+ ctx->receivedDataPos += count;
+ }
+
+ assert(ctx->receivedDataPos <= ctx->receivedDataBuffer.length);
+ assert(*processed + remaining == bufSize);
+ assert(charPtr == ((uint8_t *)data) + *processed);
+
+ if (ctx->receivedDataBuffer.data != 0 &&
+ ctx->receivedDataPos >= ctx->receivedDataBuffer.length)
+ { SSLFreeBuffer(&ctx->receivedDataBuffer);
+ ctx->receivedDataBuffer.data = 0;
+ ctx->receivedDataPos = 0;
+ }
+
+ /*
+ * This while statement causes a hang when using nonblocking low-level I/O!
+ while (remaining > 0 && ctx->state != SSL_HdskStateGracefulClose)
+ ..what we really have to do is just return as soon as we read one
+ record. A performance hit in the nonblocking case, but that is
+ the only way this code can work in both modes...
+ */
+ if (remaining > 0 && ctx->state != SSL_HdskStateGracefulClose)
+ { assert(ctx->receivedDataBuffer.data == 0);
+ if ((err = SSLReadRecord(&rec, ctx)) != 0) {
+ goto exit;
+ }
+ if (rec.contentType == SSL_RecordTypeAppData ||
+ rec.contentType == SSL_RecordTypeV2_0)
+ { if (rec.contents.length <= remaining)
+ { memcpy(charPtr, rec.contents.data, rec.contents.length);
+ remaining -= rec.contents.length;
+ charPtr += rec.contents.length;
+ *processed += rec.contents.length;
+ {
+ if ((err = SSLFreeRecord(rec, ctx))) {
+ goto exit;
+ }
+ }
+ }
+ else
+ { memcpy(charPtr, rec.contents.data, remaining);
+ charPtr += remaining;
+ *processed += remaining;
+ ctx->receivedDataBuffer = rec.contents;
+ ctx->receivedDataPos = remaining;
+ remaining = 0;
+ }
+ }
+ else {
+ if ((err = SSLProcessProtocolMessage(&rec, ctx)) != 0) {
+ /* This may not make much sense, but this is required so that we
+ process the write queue. This replicate exactly the behavior
+ before the coreTLS adoption */
+ if(err == errSSLClosedGraceful) {
+ err = SSLClose(ctx);
+ } else {
+ goto exit;
+ }
+ }
+ if ((err = SSLFreeRecord(rec, ctx))) {
+ goto exit;
+ }
+ }
+ }
+
+ err = errSecSuccess;
+
+exit:
+ /* test for renegotiate: loop until something useful happens */
+ if(((err == errSecSuccess) && (*processed == 0) && dataLength) || (err == errSSLUnexpectedRecord)) {
+ sslLogNegotiateDebug("SSLRead recursion");
+ goto readRetry;
+ }
+ /* shut down on serious errors */
+ switch(err) {
+ case errSecSuccess:
+ case errSSLWouldBlock:
+ case errSSLUnexpectedRecord:
+ case errSSLServerAuthCompleted: /* == errSSLClientAuthCompleted */
+ case errSSLClientCertRequested:
+ case errSSLClosedGraceful:
+ case errSSLClosedNoNotify:
+ break;
+ default:
+ sslErrorLog("SSLRead: going to state errorClose due to err %d\n",
+ (int)err);
+ SSLChangeHdskState(ctx, SSL_HdskStateErrorClose);
+ break;
+ }
+abort:
+ sslIoTrace(ctx, "SSLRead returns", dataLength, *processed, err);
+ return err;
+}
+
+#if SSL_DEBUG
+#include "sslCrypto.h"
+#endif
+
+OSStatus
+SSLHandshake(SSLContext *ctx)
+{
+ OSStatus err;
+
+ if(ctx == NULL) {
+ return errSecParam;
+ }
+ if (ctx->state == SSL_HdskStateGracefulClose)
+ return errSSLClosedGraceful;
+ if (ctx->state == SSL_HdskStateErrorClose)
+ return errSSLClosedAbort;
+
+ if(ctx->validCipherSuites == NULL) {
+ /* build list of legal cipherSpecs */
+ err = sslBuildCipherSuiteArray(ctx);
+ if(err) {
+ return err;
+ }
+ }
+
+ err = errSecSuccess;
+
+ if(ctx->isDTLS && ctx->timeout_deadline) {
+ CFAbsoluteTime current = CFAbsoluteTimeGetCurrent();
+
+ if (ctx->timeout_deadline<current) {
+ sslDebugLog("%p, retransmition deadline expired\n", ctx);
+ err = tls_handshake_retransmit_timer_expired(ctx->hdsk);
+ if(err) {
+ return err;
+ }
+ }
+ }
+
+ while (ctx->readCipher_ready == 0 || ctx->writeCipher_ready == 0)
+ {
+ err = SSLHandshakeProceed(ctx);
+ if((err != 0) && (err != errSSLUnexpectedRecord))
+ return err;
+ }
+
+ /* one more flush at completion of successful handshake */
+ if ((err = SSLServiceWriteQueue(ctx)) != 0) {
+ return err;
+ }
+
+ return errSecSuccess;
+}
+
+#if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR)
+
+#include <AggregateDictionary/ADClient.h>
+
+typedef void (*type_ADClientAddValueForScalarKey)(CFStringRef key, int64_t value);
+static type_ADClientAddValueForScalarKey gADClientAddValueForScalarKey = NULL;
+static dispatch_once_t gADFunctionPointersSet = 0;
+static CFBundleRef gAggdBundleRef = NULL;
+
+static bool InitializeADFunctionPointers()
+{
+ if (gADClientAddValueForScalarKey)
+ {
+ return true;
+ }
+
+ dispatch_once(&gADFunctionPointersSet,
+ ^{
+ CFStringRef path_to_aggd_framework = CFSTR("/System/Library/PrivateFrameworks/AggregateDictionary.framework");
+
+ CFURLRef aggd_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_to_aggd_framework, kCFURLPOSIXPathStyle, true);
+
+ if (NULL != aggd_url)
+ {
+ gAggdBundleRef = CFBundleCreate(kCFAllocatorDefault, aggd_url);
+ if (NULL != gAggdBundleRef)
+ {
+ gADClientAddValueForScalarKey = (type_ADClientAddValueForScalarKey)
+ CFBundleGetFunctionPointerForName(gAggdBundleRef, CFSTR("ADClientAddValueForScalarKey"));
+ }
+ CFRelease(aggd_url);
+ }
+ });
+
+ return (gADClientAddValueForScalarKey!=NULL);
+}
+
+static void ad_log_SecureTransport_early_fail(long signature)
+{
+ if (InitializeADFunctionPointers()) {
+
+ CFStringRef key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("com.apple.SecureTransport.early_fail.%ld"), signature);
+
+ if(key)
+ gADClientAddValueForScalarKey(key, 1);
+
+ CFRelease(key);
+ }
+}
+
+#endif
+
+
+#if (!TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
+
+#include <msgtracer_client.h>
+
+static void mt_log_SecureTransport_early_fail(long signature)
+{
+ char signature_string[16];
+
+ snprintf(signature_string, sizeof(signature_string), "%ld", signature);
+
+ msgtracer_log_with_keys("com.apple.SecureTransport.early_fail", ASL_LEVEL_NOTICE,
+ "com.apple.message.signature", signature_string,
+ "com.apple.message.summarize", "YES",
+ NULL);
+}
+
+#endif
+
+
+static void log_SecureTransport_early_fail(long signature)
+{
+#if (!TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
+ mt_log_SecureTransport_early_fail(signature);
+#endif
+
+#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
+ ad_log_SecureTransport_early_fail(signature);
+#endif
+}
+
+static OSStatus
+SSLHandshakeProceed(SSLContext *ctx)
+{
+ OSStatus err;
+
+
+ if(ctx->state==SSL_HdskStateUninit) {
+ /* If we are the client, we start the negotiation */
+ if(ctx->protocolSide == kSSLClientSide) {
+ err = tls_handshake_negotiate(ctx->hdsk, &ctx->peerID);
+ if(err)
+ return err;
+ }
+ SSLChangeHdskState(ctx, SSL_HdskStatePending);
+ }
+
+ if ((err = tls_handshake_continue(ctx->hdsk)) != 0)
+ return err;
+
+ if ((err = SSLServiceWriteQueue(ctx)) != 0)
+ return err;
+
+ SSLRecord rec;
+
+ err = SSLReadRecord(&rec, ctx);
+
+ if(!err) {
+ sslDebugLog("%p going to process a record (rec.len=%zd, ct=%d)\n", ctx, rec.contents.length, rec.contentType);
+ err = tls_handshake_process(ctx->hdsk, rec.contents, rec.contentType);
+ sslDebugLog("%p processed a record (rec.len=%zd, ct=%d, err=%d)\n", ctx, rec.contents.length, rec.contentType, (int)err);
+ SSLFreeRecord(rec, ctx);
+ } else if(err!=errSSLWouldBlock) {
+ sslDebugLog("%p Read error err=%d\n\n", ctx, (int)err);
+ }
+
+ if(ctx->protocolSide == kSSLClientSide &&
+ ctx->dheEnabled == false &&
+ !ctx->serverHelloReceived &&
+ err && err != errSSLWouldBlock)
+ {
+ log_SecureTransport_early_fail(err);
+ }
+
+ return err;
+}
+
+static OSStatus
+SSLProcessProtocolMessage(SSLRecord *rec, SSLContext *ctx)
+{
+ return tls_handshake_process(ctx->hdsk, rec->contents, rec->contentType);
+}
+
+OSStatus
+SSLClose(SSLContext *ctx)
+{
+ OSStatus err = errSecSuccess;
+
+ sslHdskStateDebug("SSLClose");
+ if(ctx == NULL) {
+ return errSecParam;
+ }
+
+ err = tls_handshake_close(ctx->hdsk);
+
+ if (err == 0)
+ err = SSLServiceWriteQueue(ctx);
+
+ SSLChangeHdskState(ctx, SSL_HdskStateGracefulClose);
+ if (err == errSecIO)
+ err = errSecSuccess; /* Ignore errors related to closed streams */
+ return err;
+}
+
+/*
+ * Determine how much data the client can be guaranteed to
+ * obtain via SSLRead() without blocking or causing any low-level
+ * read operations to occur.
+ *
+ * Implemented here because the relevant info in SSLContext (receivedDataBuffer
+ * and receivedDataPos) are only used in this file.
+ */
+OSStatus
+SSLGetBufferedReadSize(SSLContextRef ctx,
+ size_t *bufSize) /* RETURNED */
+{
+ if(ctx == NULL) {
+ return errSecParam;
+ }
+ if(ctx->receivedDataBuffer.data == NULL) {
+ *bufSize = 0;
+ }
+ else {
+ assert(ctx->receivedDataBuffer.length >= ctx->receivedDataPos);
+ *bufSize = ctx->receivedDataBuffer.length - ctx->receivedDataPos;
+ }
+ return errSecSuccess;
+}