X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/72a12576750f52947eb043106ba5c12c0d07decf..b1ab9ed8d0e0f1c3b66d7daa8fd5564444c56195:/libsecurity_ssl/lib/sslContext.c?ds=inline diff --git a/libsecurity_ssl/lib/sslContext.c b/libsecurity_ssl/lib/sslContext.c new file mode 100644 index 00000000..599996d5 --- /dev/null +++ b/libsecurity_ssl/lib/sslContext.c @@ -0,0 +1,2410 @@ +/* + * Copyright (c) 1999-2001,2005-2012 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@ + */ + +/* + * sslContext.c - SSLContext accessors + */ + +#include "ssl.h" +#include "sslContext.h" +#include "sslMemory.h" +#include "sslDigests.h" +#include "sslDebug.h" +#include "sslCrypto.h" + +#include "SecureTransport.h" + +#include +#include + +#include +#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) +#include +#include +#endif + +#include "sslKeychain.h" +#include "sslUtils.h" +#include "cipherSpecs.h" +#include "appleSession.h" +#include "SecureTransportPriv.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void sslFreeDnList( + SSLContext *ctx) +{ + DNListElem *dn, *nextDN; + + dn = ctx->acceptableDNList; + while (dn) + { + SSLFreeBuffer(&dn->derDN, ctx); + nextDN = dn->next; + sslFree(dn); + dn = nextDN; + } + ctx->acceptableDNList = NULL; +} + +#define min(a,b) ( ((a) < (b)) ? (a) : (b) ) +#define max(a,b) ( ((a) > (b)) ? (a) : (b) ) + +/* + * Minimum and maximum supported versions + */ +//#define MINIMUM_STREAM_VERSION SSL_Version_2_0 /* Disabled */ +#define MINIMUM_STREAM_VERSION SSL_Version_3_0 +#define MAXIMUM_STREAM_VERSION TLS_Version_1_2 +#define MINIMUM_DATAGRAM_VERSION DTLS_Version_1_0 + +/* This should be changed when we start supporting DTLS_Version_1_x */ +#define MAXIMUM_DATAGRAM_VERSION DTLS_Version_1_0 + +#define SSL_ENABLE_ECDSA_SIGN_AUTH 0 +#define SSL_ENABLE_RSA_FIXED_ECDH_AUTH 0 +#define SSL_ENABLE_ECDSA_FIXED_ECDH_AUTH 0 + +#define DEFAULT_DTLS_TIMEOUT 1 +#define DEFAULT_DTLS_MTU 1400 +#define MIN_ALLOWED_DTLS_MTU 64 /* this ensure than there will be no integer + underflow when calculating max write size */ + +static CFTypeID kSSLContextTypeID; +int kSplitDefaultValue; + +static void _sslContextDestroy(CFTypeRef arg); +static Boolean _sslContextEqual(CFTypeRef a, CFTypeRef b); +static CFHashCode _sslContextHash(CFTypeRef arg); +static CFStringRef _sslContextDescribe(CFTypeRef arg); + +static void _SSLContextReadDefault() +{ + CFTypeRef value = (CFTypeRef)CFPreferencesCopyValue(CFSTR("SSLWriteSplit"), + CFSTR("com.apple.security"), + kCFPreferencesAnyUser, + kCFPreferencesAnyHost); + if (value) { + if (CFGetTypeID(value) == CFBooleanGetTypeID()) + kSplitDefaultValue = CFBooleanGetValue((CFBooleanRef)value) ? 1 : 0; + else if (CFGetTypeID(value) == CFNumberGetTypeID()) { + if (!CFNumberGetValue((CFNumberRef)value, kCFNumberIntType, &kSplitDefaultValue)) + kSplitDefaultValue = 0; + } + if (kSplitDefaultValue < 0 || kSplitDefaultValue > 2) { + kSplitDefaultValue = 0; + } + CFRelease(value); + } + else { + kSplitDefaultValue = 0; + } +} + +static void _SSLContextRegisterClass() +{ + static const CFRuntimeClass kSSLContextRegisterClass = { + 0, /* version */ + "SSLContext", /* class name */ + NULL, /* init */ + NULL, /* copy */ + _sslContextDestroy, /* dealloc */ + _sslContextEqual, /* equal */ + _sslContextHash, /* hash */ + NULL, /* copyFormattingDesc */ + _sslContextDescribe /* copyDebugDesc */ + }; + + kSSLContextTypeID = _CFRuntimeRegisterClass(&kSSLContextRegisterClass); +} + +CFTypeID +SSLContextGetTypeID(void) +{ + static pthread_once_t sOnce = PTHREAD_ONCE_INIT; + pthread_once(&sOnce, _SSLContextRegisterClass); + return kSSLContextTypeID; +} + + +OSStatus +SSLNewContext (Boolean isServer, + SSLContextRef *contextPtr) /* RETURNED */ +{ + if(contextPtr == NULL) { + return paramErr; + } + + *contextPtr = SSLCreateContext(kCFAllocatorDefault, isServer?kSSLServerSide:kSSLClientSide, kSSLStreamType); + + if (*contextPtr == NULL) + return memFullErr; + + return noErr; +} + +SSLContextRef SSLCreateContext(CFAllocatorRef alloc, SSLProtocolSide protocolSide, SSLConnectionType connectionType) +{ + OSStatus serr = noErr; + SSLContext *ctx = (SSLContext*) _CFRuntimeCreateInstance(alloc, SSLContextGetTypeID(), sizeof(SSLContext) - sizeof(CFRuntimeBase), NULL); + + if(ctx == NULL) { + return NULL; + } + + /* subsequent errors to errOut: */ + memset(((uint8_t*) ctx) + sizeof(CFRuntimeBase), 0, sizeof(SSLContext) - sizeof(CFRuntimeBase)); + + ctx->state = SSL_HdskStateUninit; + ctx->timeout_duration = DEFAULT_DTLS_TIMEOUT; + ctx->clientCertState = kSSLClientCertNone; + + ctx->minProtocolVersion = MINIMUM_STREAM_VERSION; + ctx->maxProtocolVersion = MAXIMUM_STREAM_VERSION; + + ctx->isDTLS = false; + ctx->dtlsCookie.data = NULL; + ctx->dtlsCookie.length = 0; + ctx->hdskMessageSeq = 0; + ctx->hdskMessageSeqNext = 0; + ctx->mtu = DEFAULT_DTLS_MTU; + + ctx->negProtocolVersion = SSL_Version_Undetermined; + + + ctx->protocolSide = protocolSide; + /* Default value so we can send and receive hello msgs */ + ctx->sslTslCalls = &Ssl3Callouts; + + /* Initialize the cipher state to NULL_WITH_NULL_NULL */ + + ctx->selectedCipher = TLS_NULL_WITH_NULL_NULL; + InitCipherSpec(ctx); + ctx->writeCipher.macRef = ctx->selectedCipherSpec.macAlgorithm; + ctx->readCipher.macRef = ctx->selectedCipherSpec.macAlgorithm; + ctx->readCipher.symCipher = ctx->selectedCipherSpec.cipher; + ctx->writeCipher.symCipher = ctx->selectedCipherSpec.cipher; + + /* these two are invariant */ + ctx->writeCipher.encrypting = 1; + ctx->writePending.encrypting = 1; + + /* this gets init'd on first call to SSLHandshake() */ + ctx->validCipherSuites = NULL; + ctx->numValidCipherSuites = 0; +#if ENABLE_SSLV2 + ctx->numValidNonSSLv2Suites = 0; +#endif + + ctx->peerDomainName = NULL; + ctx->peerDomainNameLen = 0; + +#ifdef USE_CDSA_CRYPTO + /* attach to CSP, CL, TP */ + serr = attachToAll(ctx); + if(serr) { + goto errOut; + } +#endif /* USE_CDSA_CRYPTO */ + + /* Initial cert verify state: verify with default system roots */ + ctx->enableCertVerify = true; + + /* Default for RSA blinding is ENABLED */ + ctx->rsaBlindingEnable = true; + + /* Default for sending one-byte app data record is DISABLED */ + ctx->oneByteRecordEnable = false; + + /* Consult global system preference for default behavior: + * 0 = disabled, 1 = split every write, 2 = split second and subsequent writes + * (caller can override by setting kSSLSessionOptionSendOneByteRecord) + */ + static pthread_once_t sReadDefault = PTHREAD_ONCE_INIT; + pthread_once(&sReadDefault, _SSLContextReadDefault); + if (kSplitDefaultValue > 0) + ctx->oneByteRecordEnable = true; + + /* default for anonymous ciphers is DISABLED */ + ctx->anonCipherEnable = false; + + ctx->breakOnServerAuth = false; + ctx->breakOnCertRequest = false; + ctx->breakOnClientAuth = false; + ctx->signalServerAuth = false; + ctx->signalCertRequest = false; + ctx->signalClientAuth = false; + + /* + * Initial/default set of ECDH curves + */ + ctx->ecdhNumCurves = SSL_ECDSA_NUM_CURVES; + ctx->ecdhCurves[0] = SSL_Curve_secp256r1; + ctx->ecdhCurves[1] = SSL_Curve_secp384r1; + ctx->ecdhCurves[2] = SSL_Curve_secp521r1; + + ctx->ecdhPeerCurve = SSL_Curve_None; /* until we negotiate one */ + ctx->negAuthType = SSLClientAuthNone; /* ditto */ + + ctx->recordWriteQueue = NULL; + ctx->messageWriteQueue = NULL; + + if(connectionType==kSSLDatagramType) { + ctx->minProtocolVersion = MINIMUM_DATAGRAM_VERSION; + ctx->maxProtocolVersion = MAXIMUM_DATAGRAM_VERSION; + ctx->isDTLS = true; + } + + ctx->secure_renegotiation = false; + +#ifdef USE_CDSA_CRYPTO +errOut: +#endif /* USE_CDSA_CRYPTO */ + if (serr != noErr) { + CFRelease(ctx); + ctx = NULL; + } + return ctx; +} + +OSStatus +SSLNewDatagramContext (Boolean isServer, + SSLContextRef *contextPtr) /* RETURNED */ +{ + if (contextPtr == NULL) + return paramErr; + *contextPtr = SSLCreateContext(kCFAllocatorDefault, isServer?kSSLServerSide:kSSLClientSide, kSSLDatagramType); + if (*contextPtr == NULL) + return memFullErr; + return noErr; +} + +/* + * Dispose of an SSLContext. (private) + * This function is invoked after our dispatch queue is safely released, + * or directly from SSLDisposeContext if there is no dispatch queue. + */ +OSStatus +SSLDisposeContext (SSLContextRef context) +{ + if(context == NULL) { + return paramErr; + } + CFRelease(context); + return noErr; +} + +CFStringRef _sslContextDescribe(CFTypeRef arg) +{ + SSLContext* ctx = (SSLContext*) arg; + + if (ctx == NULL) { + return NULL; + } else { + CFStringRef result = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR(""), ctx); + return result; + } +} + +Boolean _sslContextEqual(CFTypeRef a, CFTypeRef b) +{ + return a == b; +} + +CFHashCode _sslContextHash(CFTypeRef arg) +{ + return (CFHashCode) arg; +} + +void _sslContextDestroy(CFTypeRef arg) +{ + SSLContext* ctx = (SSLContext*) arg; + WaitingRecord *waitRecord, *next; + +#if USE_SSLCERTIFICATE + sslDeleteCertificateChain(ctx->localCert, ctx); + sslDeleteCertificateChain(ctx->encryptCert, ctx); + sslDeleteCertificateChain(ctx->peerCert, ctx); + ctx->localCert = ctx->encryptCert = ctx->peerCert = NULL; +#else + CFReleaseNull(ctx->localCert); + CFReleaseNull(ctx->encryptCert); + CFReleaseNull(ctx->peerCert); + CFReleaseNull(ctx->trustedCerts); +#endif + + /* Free the last handshake message flight */ + SSLResetFlight(ctx); + + SSLFreeBuffer(&ctx->partialReadBuffer, ctx); + if(ctx->peerSecTrust) { + CFRelease(ctx->peerSecTrust); + ctx->peerSecTrust = NULL; + } + waitRecord = ctx->recordWriteQueue; + while (waitRecord) + { next = waitRecord->next; + sslFree(waitRecord); + waitRecord = next; + } + SSLFreeBuffer(&ctx->sessionTicket, ctx); + + #if APPLE_DH + SSLFreeBuffer(&ctx->dhParamsEncoded, ctx); +#ifdef USE_CDSA_CRYPTO + sslFreeKey(ctx->cspHand, &ctx->dhPrivate, NULL); +#else + if (ctx->secDHContext) + SecDHDestroy(ctx->secDHContext); +#endif /* !USE_CDSA_CRYPTO */ + SSLFreeBuffer(&ctx->dhPeerPublic, ctx); + SSLFreeBuffer(&ctx->dhExchangePublic, ctx); + #endif /* APPLE_DH */ + + SSLFreeBuffer(&ctx->ecdhPeerPublic, ctx); + SSLFreeBuffer(&ctx->ecdhExchangePublic, ctx); +#if USE_CDSA_CRYPTO + if(ctx->ecdhPrivCspHand == ctx->cspHand) { + sslFreeKey(ctx->ecdhPrivCspHand, &ctx->ecdhPrivate, NULL); + } + /* else we got this key from a SecKeyRef, no free needed */ +#endif + + CloseHash(&SSLHashSHA1, &ctx->shaState, ctx); + CloseHash(&SSLHashMD5, &ctx->md5State, ctx); + CloseHash(&SSLHashSHA256, &ctx->sha256State, ctx); + CloseHash(&SSLHashSHA384, &ctx->sha512State, ctx); + + SSLFreeBuffer(&ctx->sessionID, ctx); + SSLFreeBuffer(&ctx->peerID, ctx); + SSLFreeBuffer(&ctx->resumableSession, ctx); + SSLFreeBuffer(&ctx->preMasterSecret, ctx); + SSLFreeBuffer(&ctx->partialReadBuffer, ctx); + SSLFreeBuffer(&ctx->fragmentedMessageCache, ctx); + SSLFreeBuffer(&ctx->receivedDataBuffer, ctx); + + if(ctx->peerDomainName) { + sslFree(ctx->peerDomainName); + ctx->peerDomainName = NULL; + ctx->peerDomainNameLen = 0; + } + SSLDisposeCipherSuite(&ctx->readCipher, ctx); + SSLDisposeCipherSuite(&ctx->writeCipher, ctx); + SSLDisposeCipherSuite(&ctx->readPending, ctx); + SSLDisposeCipherSuite(&ctx->writePending, ctx); + + sslFree(ctx->validCipherSuites); + ctx->validCipherSuites = NULL; + ctx->numValidCipherSuites = 0; + +#if USE_CDSA_CRYPTO + /* + * NOTE: currently, all public keys come from the CL via CSSM_CL_CertGetKeyInfo. + * We really don't know what CSP the CL used to generate a public key (in fact, + * it uses the raw CSP only to get LogicalKeySizeInBits, but we can't know + * that). Thus using e.g. signingKeyCsp (or any other CSP) to free + * signingPubKey is not tecnically accurate. However, our public keys + * are all raw keys, and all Apple CSPs dispose of raw keys in the same + * way. + */ + sslFreeKey(ctx->cspHand, &ctx->signingPubKey, NULL); + sslFreeKey(ctx->cspHand, &ctx->encryptPubKey, NULL); + sslFreeKey(ctx->peerPubKeyCsp, &ctx->peerPubKey, NULL); + + if(ctx->signingPrivKeyRef) { + CFRelease(ctx->signingPrivKeyRef); + } + if(ctx->encryptPrivKeyRef) { + CFRelease(ctx->encryptPrivKeyRef); + } + if(ctx->trustedCerts) { + CFRelease(ctx->trustedCerts); + } + detachFromAll(ctx); +#else + sslFreePubKey(&ctx->signingPubKey); + sslFreePubKey(&ctx->encryptPubKey); + sslFreePubKey(&ctx->peerPubKey); + sslFreePrivKey(&ctx->signingPrivKeyRef); + sslFreePrivKey(&ctx->encryptPrivKeyRef); +#endif /* USE_CDSA_CRYPTO */ + CFReleaseSafe(ctx->acceptableCAs); + CFReleaseSafe(ctx->trustedLeafCerts); + CFReleaseSafe(ctx->localCertArray); + CFReleaseSafe(ctx->encryptCertArray); + CFReleaseSafe(ctx->encryptCertArray); + if(ctx->clientAuthTypes) { + sslFree(ctx->clientAuthTypes); + } + if(ctx->serverSigAlgs != NULL) { + sslFree(ctx->serverSigAlgs); + } + if(ctx->clientSigAlgs != NULL) { + sslFree(ctx->clientSigAlgs); + } + sslFreeDnList(ctx); + + SSLFreeBuffer(&ctx->ownVerifyData, ctx); + SSLFreeBuffer(&ctx->peerVerifyData, ctx); + + memset(((uint8_t*) ctx) + sizeof(CFRuntimeBase), 0, sizeof(SSLContext) - sizeof(CFRuntimeBase)); + + sslCleanupSession(); +} + +/* + * Determine the state of an SSL session. + */ +OSStatus +SSLGetSessionState (SSLContextRef context, + SSLSessionState *state) /* RETURNED */ +{ + SSLSessionState rtnState = kSSLIdle; + + if(context == NULL) { + return paramErr; + } + *state = rtnState; + switch(context->state) { + case SSL_HdskStateUninit: + case SSL_HdskStateServerUninit: + case SSL_HdskStateClientUninit: + rtnState = kSSLIdle; + break; + case SSL_HdskStateGracefulClose: + rtnState = kSSLClosed; + break; + case SSL_HdskStateErrorClose: + case SSL_HdskStateNoNotifyClose: + rtnState = kSSLAborted; + break; + case SSL_HdskStateServerReady: + case SSL_HdskStateClientReady: + rtnState = kSSLConnected; + break; + default: + assert((context->state >= SSL_HdskStateServerHello) && + (context->state <= SSL2_HdskStateServerFinished)); + rtnState = kSSLHandshake; + break; + + } + *state = rtnState; + return noErr; +} + +/* + * Set options for an SSL session. + */ +OSStatus +SSLSetSessionOption (SSLContextRef context, + SSLSessionOption option, + Boolean value) +{ + if(context == NULL) { + return paramErr; + } + if(sslIsSessionActive(context)) { + /* can't do this with an active session */ + return badReqErr; + } + switch(option) { + case kSSLSessionOptionBreakOnServerAuth: + context->breakOnServerAuth = value; + context->enableCertVerify = !value; + break; + case kSSLSessionOptionBreakOnCertRequested: + context->breakOnCertRequest = value; + break; + case kSSLSessionOptionBreakOnClientAuth: + context->breakOnClientAuth = value; + context->enableCertVerify = !value; + break; + case kSSLSessionOptionSendOneByteRecord: + context->oneByteRecordEnable = value; + break; + default: + return paramErr; + } + + return noErr; +} + +/* + * Determine current value for the specified option in an SSL session. + */ +OSStatus +SSLGetSessionOption (SSLContextRef context, + SSLSessionOption option, + Boolean *value) +{ + if(context == NULL || value == NULL) { + return paramErr; + } + switch(option) { + case kSSLSessionOptionBreakOnServerAuth: + *value = context->breakOnServerAuth; + break; + case kSSLSessionOptionBreakOnCertRequested: + *value = context->breakOnCertRequest; + break; + case kSSLSessionOptionBreakOnClientAuth: + *value = context->breakOnClientAuth; + break; + case kSSLSessionOptionSendOneByteRecord: + *value = context->oneByteRecordEnable; + break; + default: + return paramErr; + } + + return noErr; +} + +OSStatus +SSLSetIOFuncs (SSLContextRef ctx, + SSLReadFunc readFunc, + SSLWriteFunc writeFunc) +{ + if(ctx == NULL) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + + ctx->ioCtx.read = readFunc; + ctx->ioCtx.write = writeFunc; + return noErr; +} + +OSStatus +SSLSetConnection (SSLContextRef ctx, + SSLConnectionRef connection) +{ + if(ctx == NULL) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + + ctx->ioCtx.ioRef = connection; + return noErr; +} + +OSStatus +SSLGetConnection (SSLContextRef ctx, + SSLConnectionRef *connection) +{ + if((ctx == NULL) || (connection == NULL)) { + return paramErr; + } + *connection = ctx->ioCtx.ioRef; + return noErr; +} + +OSStatus +SSLSetPeerDomainName (SSLContextRef ctx, + const char *peerName, + size_t peerNameLen) +{ + if(ctx == NULL) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + + /* free possible existing name */ + if(ctx->peerDomainName) { + sslFree(ctx->peerDomainName); + } + + /* copy in */ + ctx->peerDomainName = (char *)sslMalloc(peerNameLen); + if(ctx->peerDomainName == NULL) { + return memFullErr; + } + memmove(ctx->peerDomainName, peerName, peerNameLen); + ctx->peerDomainNameLen = peerNameLen; + return noErr; +} + +/* + * Determine the buffer size needed for SSLGetPeerDomainName(). + */ +OSStatus +SSLGetPeerDomainNameLength (SSLContextRef ctx, + size_t *peerNameLen) // RETURNED +{ + if(ctx == NULL) { + return paramErr; + } + *peerNameLen = ctx->peerDomainNameLen; + return noErr; +} + +OSStatus +SSLGetPeerDomainName (SSLContextRef ctx, + char *peerName, // returned here + size_t *peerNameLen) // IN/OUT +{ + if(ctx == NULL) { + return paramErr; + } + if(*peerNameLen < ctx->peerDomainNameLen) { + return errSSLBufferOverflow; + } + memmove(peerName, ctx->peerDomainName, ctx->peerDomainNameLen); + *peerNameLen = ctx->peerDomainNameLen; + return noErr; +} + +OSStatus +SSLSetDatagramHelloCookie (SSLContextRef ctx, + const void *cookie, + size_t cookieLen) +{ + OSStatus err; + + if(ctx == NULL) { + return paramErr; + } + + if(!ctx->isDTLS) return paramErr; + + if((ctx == NULL) || (cookieLen>32)) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + + /* free possible existing cookie */ + if(ctx->dtlsCookie.data) { + SSLFreeBuffer(&ctx->dtlsCookie, ctx); + } + + /* copy in */ + if((err=SSLAllocBuffer(&ctx->dtlsCookie, cookieLen, ctx))!=noErr) + return err; + + memmove(ctx->dtlsCookie.data, cookie, cookieLen); + return noErr; +} + +OSStatus +SSLSetMaxDatagramRecordSize (SSLContextRef ctx, + size_t maxSize) +{ + + if(ctx == NULL) return paramErr; + if(!ctx->isDTLS) return paramErr; + if(maxSize < MIN_ALLOWED_DTLS_MTU) return paramErr; + + ctx->mtu = maxSize; + + return noErr; +} + +OSStatus +SSLGetMaxDatagramRecordSize (SSLContextRef ctx, + size_t *maxSize) +{ + if(ctx == NULL) return paramErr; + if(!ctx->isDTLS) return paramErr; + + *maxSize = ctx->mtu; + + return noErr; +} + +/* + + Keys to to math below: + + A DTLS record looks like this: | header (13 bytes) | fragment | + + For Null cipher, fragment is clear text as follows: + | Contents | Mac | + + For block cipher, fragment size must be a multiple of the cipher block size, and is the + encryption of the following plaintext : + | IV (1 block) | content | MAC | padding (0 to 255 bytes) | Padlen (1 byte) | + + The maximum content length in that case is achieved for 0 padding bytes. + +*/ + +OSStatus +SSLGetDatagramWriteSize (SSLContextRef ctx, + size_t *bufSize) +{ + if(ctx == NULL) return paramErr; + if(!ctx->isDTLS) return paramErr; + if(bufSize == NULL) return paramErr; + + size_t max_fragment_size = ctx->mtu-13; /* 13 = dtls record header */ + + UInt16 blockSize = ctx->writeCipher.symCipher->blockSize; + + if (blockSize > 0) { + /* max_fragment_size must be a multiple of blocksize */ + max_fragment_size = max_fragment_size & ~(blockSize-1); + max_fragment_size -= blockSize; /* 1 block for IV */ + max_fragment_size -= 1; /* 1 byte for pad length */ + } + + /* less the mac size */ + max_fragment_size -= ctx->writeCipher.macRef->hash->digestSize; + + /* Thats just a sanity check */ + assert(max_fragment_sizemtu); + + *bufSize = max_fragment_size; + + return noErr; +} + +static SSLProtocolVersion SSLProtocolToProtocolVersion(SSLProtocol protocol) { + switch (protocol) { + case kSSLProtocol2: return SSL_Version_2_0; + case kSSLProtocol3: return SSL_Version_3_0; + case kTLSProtocol1: return TLS_Version_1_0; + case kTLSProtocol11: return TLS_Version_1_1; + case kTLSProtocol12: return TLS_Version_1_2; + case kDTLSProtocol1: return DTLS_Version_1_0; + default: return SSL_Version_Undetermined; + } +} + +/* concert between private SSLProtocolVersion and public SSLProtocol */ +static SSLProtocol SSLProtocolVersionToProtocol(SSLProtocolVersion version) +{ + switch(version) { + case SSL_Version_2_0: return kSSLProtocol2; + case SSL_Version_3_0: return kSSLProtocol3; + case TLS_Version_1_0: return kTLSProtocol1; + case TLS_Version_1_1: return kTLSProtocol11; + case TLS_Version_1_2: return kTLSProtocol12; + case DTLS_Version_1_0: return kDTLSProtocol1; + default: + sslErrorLog("SSLProtocolVersionToProtocol: bad prot (%04x)\n", + version); + /* DROPTHROUGH */ + case SSL_Version_Undetermined: return kSSLProtocolUnknown; + } +} + +OSStatus +SSLSetProtocolVersionMin (SSLContextRef ctx, + SSLProtocol minVersion) +{ + if(ctx == NULL) return paramErr; + + SSLProtocolVersion version = SSLProtocolToProtocolVersion(minVersion); + if (ctx->isDTLS) { + if (version > MINIMUM_DATAGRAM_VERSION || + version < MAXIMUM_DATAGRAM_VERSION) + return errSSLIllegalParam; + if (version < ctx->maxProtocolVersion) + ctx->maxProtocolVersion = version; + } else { + if (version < MINIMUM_STREAM_VERSION || version > MAXIMUM_STREAM_VERSION) + return errSSLIllegalParam; + if (version > ctx->maxProtocolVersion) + ctx->maxProtocolVersion = version; + } + ctx->minProtocolVersion = version; + + return noErr; +} + +OSStatus +SSLGetProtocolVersionMin (SSLContextRef ctx, + SSLProtocol *minVersion) +{ + if(ctx == NULL) return paramErr; + + *minVersion = SSLProtocolVersionToProtocol(ctx->minProtocolVersion); + return noErr; +} + +OSStatus +SSLSetProtocolVersionMax (SSLContextRef ctx, + SSLProtocol maxVersion) +{ + if(ctx == NULL) return paramErr; + + SSLProtocolVersion version = SSLProtocolToProtocolVersion(maxVersion); + if (ctx->isDTLS) { + if (version > MINIMUM_DATAGRAM_VERSION || + version < MAXIMUM_DATAGRAM_VERSION) + return errSSLIllegalParam; + if (version > ctx->minProtocolVersion) + ctx->minProtocolVersion = version; + } else { + if (version < MINIMUM_STREAM_VERSION || version > MAXIMUM_STREAM_VERSION) + return errSSLIllegalParam; + if (version < ctx->minProtocolVersion) + ctx->minProtocolVersion = version; + } + ctx->maxProtocolVersion = version; + + return noErr; +} + +OSStatus +SSLGetProtocolVersionMax (SSLContextRef ctx, + SSLProtocol *maxVersion) +{ + if(ctx == NULL) return paramErr; + + *maxVersion = SSLProtocolVersionToProtocol(ctx->maxProtocolVersion); + return noErr; +} + + +OSStatus +SSLSetProtocolVersionEnabled(SSLContextRef ctx, + SSLProtocol protocol, + Boolean enable) +{ + if(ctx == NULL) { + return paramErr; + } + if(sslIsSessionActive(ctx) || ctx->isDTLS) { + /* Can't do this with an active session, nor with a DTLS session */ + return badReqErr; + } + if (protocol == kSSLProtocolAll) { + if (enable) { + ctx->minProtocolVersion = MINIMUM_STREAM_VERSION; + ctx->maxProtocolVersion = MAXIMUM_STREAM_VERSION; + } else { + ctx->minProtocolVersion = SSL_Version_Undetermined; + ctx->maxProtocolVersion = SSL_Version_Undetermined; + } + } else { + SSLProtocolVersion version = SSLProtocolToProtocolVersion(protocol); + if (enable) { + if (version < MINIMUM_STREAM_VERSION || version > MAXIMUM_STREAM_VERSION) { + return paramErr; + } + if (version > ctx->maxProtocolVersion) { + ctx->maxProtocolVersion = version; + if (ctx->minProtocolVersion == SSL_Version_Undetermined) + ctx->minProtocolVersion = version; + } + if (version < ctx->minProtocolVersion) { + ctx->minProtocolVersion = version; + } + } else { + if (version < SSL_Version_2_0 || version > MAXIMUM_STREAM_VERSION) { + return paramErr; + } + /* Disabling a protocol version now resets the minimum acceptable + * version to the next higher version. This means it's no longer + * possible to enable a discontiguous set of protocol versions. + */ + SSLProtocolVersion nextVersion; + switch (version) { + case SSL_Version_2_0: + nextVersion = SSL_Version_3_0; + break; + case SSL_Version_3_0: + nextVersion = TLS_Version_1_0; + break; + case TLS_Version_1_0: + nextVersion = TLS_Version_1_1; + break; + case TLS_Version_1_1: + nextVersion = TLS_Version_1_2; + break; + case TLS_Version_1_2: + default: + nextVersion = SSL_Version_Undetermined; + break; + } + ctx->minProtocolVersion = max(ctx->minProtocolVersion, nextVersion); + if (ctx->minProtocolVersion > ctx->maxProtocolVersion) { + ctx->minProtocolVersion = SSL_Version_Undetermined; + ctx->maxProtocolVersion = SSL_Version_Undetermined; + } + } + } + + return noErr; +} + +OSStatus +SSLGetProtocolVersionEnabled(SSLContextRef ctx, + SSLProtocol protocol, + Boolean *enable) /* RETURNED */ +{ + if(ctx == NULL) { + return paramErr; + } + if(ctx->isDTLS) { + /* Can't do this with a DTLS session */ + return badReqErr; + } + switch(protocol) { + case kSSLProtocol2: + case kSSLProtocol3: + case kTLSProtocol1: + case kTLSProtocol11: + case kTLSProtocol12: + { + SSLProtocolVersion version = SSLProtocolToProtocolVersion(protocol); + *enable = (ctx->minProtocolVersion <= version + && ctx->maxProtocolVersion >= version); + break; + } + case kSSLProtocolAll: + *enable = (ctx->minProtocolVersion <= MINIMUM_STREAM_VERSION + && ctx->maxProtocolVersion >= MAXIMUM_STREAM_VERSION); + break; + default: + return paramErr; + } + return noErr; +} + +/* deprecated */ +OSStatus +SSLSetProtocolVersion (SSLContextRef ctx, + SSLProtocol version) +{ + if(ctx == NULL) { + return paramErr; + } + if(sslIsSessionActive(ctx) || ctx->isDTLS) { + /* Can't do this with an active session, nor with a DTLS session */ + return badReqErr; + } + + switch(version) { + case kSSLProtocol3: + /* this tells us to do our best, up to 3.0 */ + ctx->minProtocolVersion = MINIMUM_STREAM_VERSION; + ctx->maxProtocolVersion = SSL_Version_3_0; + break; + case kSSLProtocol3Only: + ctx->minProtocolVersion = SSL_Version_3_0; + ctx->maxProtocolVersion = SSL_Version_3_0; + break; + case kTLSProtocol1: + /* this tells us to do our best, up to TLS, but allows 3.0 */ + ctx->minProtocolVersion = MINIMUM_STREAM_VERSION; + ctx->maxProtocolVersion = TLS_Version_1_0; + break; + case kTLSProtocol1Only: + ctx->minProtocolVersion = TLS_Version_1_0; + ctx->maxProtocolVersion = TLS_Version_1_0; + break; + case kTLSProtocol11: + /* This tells us to do our best, up to TLS 1.1, currently also + allows 3.0 or TLS 1.0 */ + ctx->minProtocolVersion = MINIMUM_STREAM_VERSION; + ctx->maxProtocolVersion = TLS_Version_1_1; + break; + case kTLSProtocol12: + case kSSLProtocolAll: + case kSSLProtocolUnknown: + /* This tells us to do our best, up to TLS 1.2, currently also + allows 3.0 or TLS 1.0 or TLS 1.1 */ + ctx->minProtocolVersion = MINIMUM_STREAM_VERSION; + ctx->maxProtocolVersion = MAXIMUM_STREAM_VERSION; + break; + default: + return paramErr; + } + + return noErr; +} + +/* deprecated */ +OSStatus +SSLGetProtocolVersion (SSLContextRef ctx, + SSLProtocol *protocol) /* RETURNED */ +{ + if(ctx == NULL) { + return paramErr; + } + /* translate array of booleans to public value; not all combinations + * are legal (i.e., meaningful) for this call */ + if (ctx->maxProtocolVersion == MAXIMUM_STREAM_VERSION) { + if(ctx->minProtocolVersion == MINIMUM_STREAM_VERSION) { + /* traditional 'all enabled' */ + *protocol = kSSLProtocolAll; + return noErr; + } + } else if (ctx->maxProtocolVersion == TLS_Version_1_1) { + if(ctx->minProtocolVersion == MINIMUM_STREAM_VERSION) { + /* traditional 'all enabled' */ + *protocol = kTLSProtocol11; + return noErr; + } + } else if (ctx->maxProtocolVersion == TLS_Version_1_0) { + if(ctx->minProtocolVersion == MINIMUM_STREAM_VERSION) { + /* TLS1.1 and below enabled */ + *protocol = kTLSProtocol1; + return noErr; + } else if(ctx->minProtocolVersion == TLS_Version_1_0) { + *protocol = kTLSProtocol1Only; + } + } else if(ctx->maxProtocolVersion == SSL_Version_3_0) { + if(ctx->minProtocolVersion == MINIMUM_STREAM_VERSION) { + /* Could also return kSSLProtocol3Only since + MINIMUM_STREAM_VERSION == SSL_Version_3_0. */ + *protocol = kSSLProtocol3; + return noErr; + } + } + + return paramErr; +} + +OSStatus +SSLGetNegotiatedProtocolVersion (SSLContextRef ctx, + SSLProtocol *protocol) /* RETURNED */ +{ + if(ctx == NULL) { + return paramErr; + } + *protocol = SSLProtocolVersionToProtocol(ctx->negProtocolVersion); + return noErr; +} + +OSStatus +SSLSetEnableCertVerify (SSLContextRef ctx, + Boolean enableVerify) +{ + if(ctx == NULL) { + return paramErr; + } + sslCertDebug("SSLSetEnableCertVerify %s", + enableVerify ? "true" : "false"); + if(sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + ctx->enableCertVerify = enableVerify; + return noErr; +} + +OSStatus +SSLGetEnableCertVerify (SSLContextRef ctx, + Boolean *enableVerify) +{ + if(ctx == NULL) { + return paramErr; + } + *enableVerify = ctx->enableCertVerify; + return noErr; +} + +OSStatus +SSLSetAllowsExpiredCerts(SSLContextRef ctx, + Boolean allowExpired) +{ + if(ctx == NULL) { + return paramErr; + } + sslCertDebug("SSLSetAllowsExpiredCerts %s", + allowExpired ? "true" : "false"); + if(sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + ctx->allowExpiredCerts = allowExpired; + return noErr; +} + +OSStatus +SSLGetAllowsExpiredCerts (SSLContextRef ctx, + Boolean *allowExpired) +{ + if(ctx == NULL) { + return paramErr; + } + *allowExpired = ctx->allowExpiredCerts; + return noErr; +} + +OSStatus +SSLSetAllowsExpiredRoots(SSLContextRef ctx, + Boolean allowExpired) +{ + if(ctx == NULL) { + return paramErr; + } + sslCertDebug("SSLSetAllowsExpiredRoots %s", + allowExpired ? "true" : "false"); + if(sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + ctx->allowExpiredRoots = allowExpired; + return noErr; +} + +OSStatus +SSLGetAllowsExpiredRoots (SSLContextRef ctx, + Boolean *allowExpired) +{ + if(ctx == NULL) { + return paramErr; + } + *allowExpired = ctx->allowExpiredRoots; + return noErr; +} + +OSStatus SSLSetAllowsAnyRoot( + SSLContextRef ctx, + Boolean anyRoot) +{ + if(ctx == NULL) { + return paramErr; + } + sslCertDebug("SSLSetAllowsAnyRoot %s", anyRoot ? "true" : "false"); + ctx->allowAnyRoot = anyRoot; + return noErr; +} + +OSStatus +SSLGetAllowsAnyRoot( + SSLContextRef ctx, + Boolean *anyRoot) +{ + if(ctx == NULL) { + return paramErr; + } + *anyRoot = ctx->allowAnyRoot; + return noErr; +} + +#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) +/* obtain the system roots sets for this app, policy SSL */ +static OSStatus sslDefaultSystemRoots( + SSLContextRef ctx, + CFArrayRef *systemRoots) // created and RETURNED + +{ + return SecTrustSettingsCopyQualifiedCerts(&CSSMOID_APPLE_TP_SSL, + NULL, true, // application - us + ctx->peerDomainName, + ctx->peerDomainNameLen, + (ctx->protocolSide == kSSLServerSide) ? + /* server verifies, client encrypts */ + CSSM_KEYUSE_VERIFY : CSSM_KEYUSE_ENCRYPT, + systemRoots); +} +#endif /* OS X only */ + +OSStatus +SSLSetTrustedRoots (SSLContextRef ctx, + CFArrayRef trustedRoots, + Boolean replaceExisting) +{ +#ifdef USE_CDSA_CRYPTO + if(ctx == NULL) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + + if(replaceExisting) { + /* trivial case - retain the new, throw out the old. */ + if (trustedRoots) + CFRetain(trustedRoots); + CFReleaseSafe(ctx->trustedCerts); + ctx->trustedCerts = trustedRoots; + return noErr; + } + + /* adding new trusted roots - to either our existing set, or the system set */ + CFArrayRef existingRoots = NULL; + OSStatus ortn; + if(ctx->trustedCerts != NULL) { + /* we'll release these as we exit */ + existingRoots = ctx->trustedCerts; + } + else { + /* get system set for this app, policy SSL */ + ortn = sslDefaultSystemRoots(ctx, &existingRoots); + if(ortn) { + CFReleaseSafe(existingRoots); + return ortn; + } + } + + /* Create a new root array with caller's roots first */ + CFMutableArrayRef newRoots = CFArrayCreateMutableCopy(NULL, 0, trustedRoots); + CFRange existRange = { 0, CFArrayGetCount(existingRoots) }; + CFArrayAppendArray(newRoots, existingRoots, existRange); + CFRelease(existingRoots); + ctx->trustedCerts = newRoots; + return noErr; + +#else + if (sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + sslCertDebug("SSLSetTrustedRoot numCerts %d replaceExist %s", + (int)CFArrayGetCount(trustedRoots), replaceExisting ? "true" : "false"); + + if (replaceExisting) { + ctx->trustedCertsOnly = true; + CFReleaseNull(ctx->trustedCerts); + } + + if (ctx->trustedCerts) { + CFIndex count = CFArrayGetCount(trustedRoots); + CFRange range = { 0, count }; + CFArrayAppendArray(ctx->trustedCerts, trustedRoots, range); + } else { + require(ctx->trustedCerts = + CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, trustedRoots), + errOut); + } + + return noErr; + +errOut: + return memFullErr; +#endif /* !USE_CDSA_CRYPTO */ +} + +OSStatus +SSLCopyTrustedRoots (SSLContextRef ctx, + CFArrayRef *trustedRoots) /* RETURNED */ +{ + if(ctx == NULL || trustedRoots == NULL) { + return paramErr; + } + if(ctx->trustedCerts != NULL) { + *trustedRoots = ctx->trustedCerts; + CFRetain(ctx->trustedCerts); + return noErr; + } +#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) + /* use default system roots */ + return sslDefaultSystemRoots(ctx, trustedRoots); +#else + *trustedRoots = NULL; + return noErr; +#endif +} + +/* legacy version, caller must CFRelease each cert */ +OSStatus +SSLGetTrustedRoots (SSLContextRef ctx, + CFArrayRef *trustedRoots) /* RETURNED */ +{ + OSStatus ortn; + + if((ctx == NULL) || (trustedRoots == NULL)) { + return paramErr; + } + + ortn = SSLCopyTrustedRoots(ctx, trustedRoots); + if(ortn) { + return ortn; + } + /* apply the legacy bug */ + CFIndex numCerts = CFArrayGetCount(*trustedRoots); + CFIndex dex; + for(dex=0; dextrustedLeafCerts) { + CFRelease(ctx->trustedLeafCerts); + } + ctx->trustedLeafCerts = trustedCerts; + CFRetain(trustedCerts); + return noErr; +} + +OSStatus +SSLCopyTrustedLeafCertificates (SSLContextRef ctx, + CFArrayRef *trustedCerts) /* RETURNED */ +{ + if(ctx == NULL) { + return paramErr; + } + if(ctx->trustedLeafCerts != NULL) { + *trustedCerts = ctx->trustedLeafCerts; + CFRetain(ctx->trustedCerts); + return noErr; + } + *trustedCerts = NULL; + return noErr; +} + +OSStatus +SSLSetClientSideAuthenticate (SSLContext *ctx, + SSLAuthenticate auth) +{ + if(ctx == NULL) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + ctx->clientAuth = auth; + switch(auth) { + case kNeverAuthenticate: + ctx->tryClientAuth = false; + break; + case kAlwaysAuthenticate: + case kTryAuthenticate: + ctx->tryClientAuth = true; + break; + } + return noErr; +} + +OSStatus +SSLGetClientSideAuthenticate (SSLContext *ctx, + SSLAuthenticate *auth) /* RETURNED */ +{ + if(ctx == NULL || auth == NULL) { + return paramErr; + } + *auth = ctx->clientAuth; + return noErr; +} + +OSStatus +SSLGetClientCertificateState (SSLContextRef ctx, + SSLClientCertificateState *clientState) +{ + if(ctx == NULL) { + return paramErr; + } + *clientState = ctx->clientCertState; + return noErr; +} + +OSStatus +SSLSetCertificate (SSLContextRef ctx, + CFArrayRef certRefs) +{ + /* + * -- free localCerts if we have any + * -- Get raw cert data, convert to ctx->localCert + * -- get pub, priv keys from certRef[0] + * -- validate cert chain + */ + if(ctx == NULL) { + return paramErr; + } + + /* can't do this with an active session */ + if(sslIsSessionActive(ctx) && + /* kSSLClientCertRequested implies client side */ + (ctx->clientCertState != kSSLClientCertRequested)) + { + return badReqErr; + } + CFReleaseNull(ctx->localCertArray); + /* changing the client cert invalidates negotiated auth type */ + ctx->negAuthType = SSLClientAuthNone; + if(certRefs == NULL) { + return noErr; // we have cleared the cert, as requested + } + OSStatus ortn = parseIncomingCerts(ctx, + certRefs, + &ctx->localCert, + &ctx->signingPubKey, + &ctx->signingPrivKeyRef, + &ctx->ourSignerAlg); + if(ortn == noErr) { + ctx->localCertArray = certRefs; + CFRetain(certRefs); + /* client cert was changed, must update auth type */ + ortn = SSLUpdateNegotiatedClientAuthType(ctx); + } + return ortn; +} + +OSStatus +SSLSetEncryptionCertificate (SSLContextRef ctx, + CFArrayRef certRefs) +{ + /* + * -- free encryptCert if we have any + * -- Get raw cert data, convert to ctx->encryptCert + * -- get pub, priv keys from certRef[0] + * -- validate cert chain + */ + if(ctx == NULL) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + CFReleaseNull(ctx->encryptCertArray); + OSStatus ortn = parseIncomingCerts(ctx, + certRefs, + &ctx->encryptCert, + &ctx->encryptPubKey, + &ctx->encryptPrivKeyRef, + NULL); /* Signer alg */ + if(ortn == noErr) { + ctx->encryptCertArray = certRefs; + CFRetain(certRefs); + } + return ortn; +} + +OSStatus SSLGetCertificate(SSLContextRef ctx, + CFArrayRef *certRefs) +{ + if(ctx == NULL) { + return paramErr; + } + *certRefs = ctx->localCertArray; + return noErr; +} + +OSStatus SSLGetEncryptionCertificate(SSLContextRef ctx, + CFArrayRef *certRefs) +{ + if(ctx == NULL) { + return paramErr; + } + *certRefs = ctx->encryptCertArray; + return noErr; +} + +OSStatus +SSLSetPeerID (SSLContext *ctx, + const void *peerID, + size_t peerIDLen) +{ + OSStatus serr; + + /* copy peerId to context->peerId */ + if((ctx == NULL) || + (peerID == NULL) || + (peerIDLen == 0)) { + return paramErr; + } + if(sslIsSessionActive(ctx) && + /* kSSLClientCertRequested implies client side */ + (ctx->clientCertState != kSSLClientCertRequested)) + { + return badReqErr; + } + SSLFreeBuffer(&ctx->peerID, ctx); + serr = SSLAllocBuffer(&ctx->peerID, peerIDLen, ctx); + if(serr) { + return serr; + } + memmove(ctx->peerID.data, peerID, peerIDLen); + return noErr; +} + +OSStatus +SSLGetPeerID (SSLContextRef ctx, + const void **peerID, + size_t *peerIDLen) +{ + *peerID = ctx->peerID.data; // may be NULL + *peerIDLen = ctx->peerID.length; + return noErr; +} + +OSStatus +SSLGetNegotiatedCipher (SSLContextRef ctx, + SSLCipherSuite *cipherSuite) +{ + if(ctx == NULL) { + return paramErr; + } + if(!sslIsSessionActive(ctx)) { + return badReqErr; + } + *cipherSuite = (SSLCipherSuite)ctx->selectedCipher; + return noErr; +} + +/* + * Add an acceptable distinguished name (client authentication only). + */ +OSStatus +SSLAddDistinguishedName( + SSLContextRef ctx, + const void *derDN, + size_t derDNLen) +{ + DNListElem *dn; + OSStatus err; + + if(ctx == NULL) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + return badReqErr; + } + + dn = (DNListElem *)sslMalloc(sizeof(DNListElem)); + if(dn == NULL) { + return memFullErr; + } + if ((err = SSLAllocBuffer(&dn->derDN, derDNLen, ctx)) != 0) + return err; + memcpy(dn->derDN.data, derDN, derDNLen); + dn->next = ctx->acceptableDNList; + ctx->acceptableDNList = dn; + return noErr; +} + +/* single-cert version of SSLSetCertificateAuthorities() */ +static OSStatus +sslAddCA(SSLContextRef ctx, + SecCertificateRef cert) +{ + OSStatus ortn = paramErr; + CFDataRef subjectName; + + /* Get subject from certificate. */ + require(subjectName = SecCertificateCopySubjectSequence(cert), errOut); + /* add to acceptableCAs as cert, creating array if necessary */ + if(ctx->acceptableCAs == NULL) { + require(ctx->acceptableCAs = CFArrayCreateMutable(NULL, 0, + &kCFTypeArrayCallBacks), errOut); + if(ctx->acceptableCAs == NULL) { + return memFullErr; + } + } + CFArrayAppendValue(ctx->acceptableCAs, cert); + + /* then add this cert's subject name to acceptableDNList */ + ortn = SSLAddDistinguishedName(ctx, CFDataGetBytePtr(subjectName), + CFDataGetLength(subjectName)); +errOut: + CFReleaseSafe(subjectName); + return ortn; +} + +/* + * Add a SecCertificateRef, or a CFArray of them, to a server's list + * of acceptable Certificate Authorities (CAs) to present to the client + * when client authentication is performed. + */ +OSStatus +SSLSetCertificateAuthorities(SSLContextRef ctx, + CFTypeRef certificateOrArray, + Boolean replaceExisting) +{ + CFTypeID itemType; + OSStatus ortn = noErr; + + if((ctx == NULL) || sslIsSessionActive(ctx) || + (ctx->protocolSide != kSSLServerSide)) { + return paramErr; + } + if(replaceExisting) { + sslFreeDnList(ctx); + if(ctx->acceptableCAs) { + CFRelease(ctx->acceptableCAs); + ctx->acceptableCAs = NULL; + } + } + /* else appending */ + + itemType = CFGetTypeID(certificateOrArray); + if(itemType == SecCertificateGetTypeID()) { + /* one cert */ + ortn = sslAddCA(ctx, (SecCertificateRef)certificateOrArray); + } + else if(itemType == CFArrayGetTypeID()) { + CFArrayRef cfa = (CFArrayRef)certificateOrArray; + CFIndex numCerts = CFArrayGetCount(cfa); + CFIndex dex; + + /* array of certs */ + for(dex=0; dexacceptableCAs == NULL) { + *certificates = NULL; + return noErr; + } + *certificates = ctx->acceptableCAs; + CFRetain(ctx->acceptableCAs); + return noErr; +} + + +/* + * Obtain the list of acceptable distinguished names as provided by + * a server (if the SSLCotextRef is configured as a client), or as + * specified by SSLSetCertificateAuthorities() (if the SSLContextRef + * is configured as a server). + */ +OSStatus +SSLCopyDistinguishedNames (SSLContextRef ctx, + CFArrayRef *names) +{ + CFMutableArrayRef outArray = NULL; + DNListElem *dn; + + if((ctx == NULL) || (names == NULL)) { + return paramErr; + } + if(ctx->acceptableDNList == NULL) { + *names = NULL; + return noErr; + } + outArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + dn = ctx->acceptableDNList; + while (dn) { + CFDataRef cfDn = CFDataCreate(NULL, dn->derDN.data, dn->derDN.length); + CFArrayAppendValue(outArray, cfDn); + CFRelease(cfDn); + dn = dn->next; + } + *names = outArray; + return noErr; +} + + +/* + * Request peer certificates. Valid anytime, subsequent to + * a handshake attempt. + * Common code for SSLGetPeerCertificates() and SSLCopyPeerCertificates(). + */ +static OSStatus +sslCopyPeerCertificates (SSLContextRef ctx, + CFArrayRef *certs, + Boolean legacy) +{ + if(ctx == NULL) { + return paramErr; + } + +#ifdef USE_SSLCERTIFICATE + uint32 numCerts; + CFMutableArrayRef ca; + CFIndex i; + SecCertificateRef cfd; + OSStatus ortn; + CSSM_DATA certData; + SSLCertificate *scert; + + *certs = NULL; + + /* + * Copy peerCert, a chain of SSLCertificates, to a CFArray of + * CFDataRefs, each of which is one DER-encoded cert. + */ + numCerts = SSLGetCertificateChainLength(ctx->peerCert); + if(numCerts == 0) { + return noErr; + } + ca = CFArrayCreateMutable(kCFAllocatorDefault, + (CFIndex)numCerts, &kCFTypeArrayCallBacks); + if(ca == NULL) { + return memFullErr; + } + + /* + * Caller gets leaf cert first, the opposite of the way we store them. + */ + scert = ctx->peerCert; + for(i=0; (unsigned)iderCert, &certData); + ortn = SecCertificateCreateFromData(&certData, + CSSM_CERT_X_509v3, + CSSM_CERT_ENCODING_DER, + &cfd); + if(ortn) { + CFRelease(ca); + return ortn; + } + /* insert at head of array */ + CFArrayInsertValueAtIndex(ca, 0, cfd); + if(!legacy) { + /* skip for legacy SSLGetPeerCertificates() */ + CFRelease(cfd); + } + scert = scert->next; + } + *certs = ca; + +#else + if (!ctx->peerCert) { + *certs = NULL; + return badReqErr; + } + + CFArrayRef ca = CFArrayCreateCopy(kCFAllocatorDefault, ctx->peerCert); + *certs = ca; + if (ca == NULL) { + return memFullErr; + } + + if (legacy) { + CFIndex ix, count = CFArrayGetCount(ca); + for (ix = 0; ix < count; ++ix) { + CFRetain(CFArrayGetValueAtIndex(ca, ix)); + } + } +#endif + + return noErr; +} + +OSStatus +SSLCopyPeerCertificates (SSLContextRef ctx, + CFArrayRef *certs) +{ + return sslCopyPeerCertificates(ctx, certs, false); +} + +OSStatus +SSLGetPeerCertificates (SSLContextRef ctx, + CFArrayRef *certs) + { + return sslCopyPeerCertificates(ctx, certs, true); + } + + +/* + * Specify Diffie-Hellman parameters. Optional; if we are configured to allow + * for D-H ciphers and a D-H cipher is negotiated, and this function has not + * been called, a set of process-wide parameters will be calculated. However + * that can take a long time (30 seconds). + */ +OSStatus SSLSetDiffieHellmanParams( + SSLContextRef ctx, + const void *dhParams, + size_t dhParamsLen) +{ +#if APPLE_DH + if(ctx == NULL) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + return badReqErr; + } + SSLFreeBuffer(&ctx->dhParamsEncoded, ctx); +#if !USE_CDSA_CRYPTO + if (ctx->secDHContext) + SecDHDestroy(ctx->secDHContext); +#endif + + OSStatus ortn; + ortn = SSLCopyBufferFromData(dhParams, dhParamsLen, + &ctx->dhParamsEncoded); + + return ortn; + +#endif /* APPLE_DH */ +} + +/* + * Return parameter block specified in SSLSetDiffieHellmanParams. + * Returned data is not copied and belongs to the SSLContextRef. + */ +OSStatus SSLGetDiffieHellmanParams( + SSLContextRef ctx, + const void **dhParams, + size_t *dhParamsLen) +{ +#if APPLE_DH + if(ctx == NULL) { + return paramErr; + } + *dhParams = ctx->dhParamsEncoded.data; + *dhParamsLen = ctx->dhParamsEncoded.length; + return noErr; +#else + return unimpErr; +#endif /* APPLE_DH */ +} + +OSStatus SSLSetRsaBlinding( + SSLContextRef ctx, + Boolean blinding) +{ + if(ctx == NULL) { + return paramErr; + } + ctx->rsaBlindingEnable = blinding; + return noErr; +} + +OSStatus SSLGetRsaBlinding( + SSLContextRef ctx, + Boolean *blinding) +{ + if(ctx == NULL) { + return paramErr; + } + *blinding = ctx->rsaBlindingEnable; + return noErr; +} + +OSStatus +SSLCopyPeerTrust( + SSLContextRef ctx, + SecTrustRef *trust) /* RETURNED */ +{ + OSStatus status = noErr; + if (ctx == NULL || trust == NULL) + return paramErr; + + /* Create a SecTrustRef if this was a resumed session and we + didn't have one yet. */ + if (!ctx->peerSecTrust && ctx->peerCert) { + status = sslCreateSecTrust(ctx, ctx->peerCert, true, + &ctx->peerSecTrust); + } + + *trust = ctx->peerSecTrust; + if (ctx->peerSecTrust) + CFRetain(ctx->peerSecTrust); + + return status; +} + +OSStatus SSLGetPeerSecTrust( + SSLContextRef ctx, + SecTrustRef *trust) /* RETURNED */ +{ + OSStatus status = noErr; + if (ctx == NULL || trust == NULL) + return paramErr; + + /* Create a SecTrustRef if this was a resumed session and we + didn't have one yet. */ + if (!ctx->peerSecTrust && ctx->peerCert) { + status = sslCreateSecTrust(ctx, ctx->peerCert, true, + &ctx->peerSecTrust); + } + + *trust = ctx->peerSecTrust; + return status; +} + +OSStatus SSLInternalMasterSecret( + SSLContextRef ctx, + void *secret, // mallocd by caller, SSL_MASTER_SECRET_SIZE + size_t *secretSize) // in/out +{ + if((ctx == NULL) || (secret == NULL) || (secretSize == NULL)) { + return paramErr; + } + if(*secretSize < SSL_MASTER_SECRET_SIZE) { + return paramErr; + } + memmove(secret, ctx->masterSecret, SSL_MASTER_SECRET_SIZE); + *secretSize = SSL_MASTER_SECRET_SIZE; + return noErr; +} + +OSStatus SSLInternalServerRandom( + SSLContextRef ctx, + void *randBuf, // mallocd by caller, SSL_CLIENT_SRVR_RAND_SIZE + size_t *randSize) // in/out +{ + if((ctx == NULL) || (randBuf == NULL) || (randSize == NULL)) { + return paramErr; + } + if(*randSize < SSL_CLIENT_SRVR_RAND_SIZE) { + return paramErr; + } + memmove(randBuf, ctx->serverRandom, SSL_CLIENT_SRVR_RAND_SIZE); + *randSize = SSL_CLIENT_SRVR_RAND_SIZE; + return noErr; +} + +OSStatus SSLInternalClientRandom( + SSLContextRef ctx, + void *randBuf, // mallocd by caller, SSL_CLIENT_SRVR_RAND_SIZE + size_t *randSize) // in/out +{ + if((ctx == NULL) || (randBuf == NULL) || (randSize == NULL)) { + return paramErr; + } + if(*randSize < SSL_CLIENT_SRVR_RAND_SIZE) { + return paramErr; + } + memmove(randBuf, ctx->clientRandom, SSL_CLIENT_SRVR_RAND_SIZE); + *randSize = SSL_CLIENT_SRVR_RAND_SIZE; + return noErr; +} + +OSStatus SSLGetCipherSizes( + SSLContextRef ctx, + size_t *digestSize, + size_t *symmetricKeySize, + size_t *ivSize) +{ + const SSLCipherSpec *currCipher; + + if((ctx == NULL) || (digestSize == NULL) || + (symmetricKeySize == NULL) || (ivSize == NULL)) { + return paramErr; + } + currCipher = &ctx->selectedCipherSpec; + *digestSize = currCipher->macAlgorithm->hash->digestSize; + *symmetricKeySize = currCipher->cipher->secretKeySize; + *ivSize = currCipher->cipher->ivSize; + return noErr; +} + +OSStatus +SSLGetResumableSessionInfo( + SSLContextRef ctx, + Boolean *sessionWasResumed, // RETURNED + void *sessionID, // RETURNED, mallocd by caller + size_t *sessionIDLength) // IN/OUT +{ + if((ctx == NULL) || (sessionWasResumed == NULL) || + (sessionID == NULL) || (sessionIDLength == NULL) || + (*sessionIDLength < MAX_SESSION_ID_LENGTH)) { + return paramErr; + } + if(ctx->sessionMatch) { + *sessionWasResumed = true; + if(ctx->sessionID.length > *sessionIDLength) { + /* really should never happen - means ID > 32 */ + return paramErr; + } + if(ctx->sessionID.length) { + /* + * Note PAC-based session resumption can result in sessionMatch + * with no sessionID + */ + memmove(sessionID, ctx->sessionID.data, ctx->sessionID.length); + } + *sessionIDLength = ctx->sessionID.length; + } + else { + *sessionWasResumed = false; + *sessionIDLength = 0; + } + return noErr; +} + +/* + * Get/set enable of anonymous ciphers. Default is enabled. + */ +OSStatus +SSLSetAllowAnonymousCiphers( + SSLContextRef ctx, + Boolean enable) +{ + if(ctx == NULL) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + return badReqErr; + } + if(ctx->validCipherSuites != NULL) { + /* SSLSetEnabledCiphers() has already been called */ + return badReqErr; + } + ctx->anonCipherEnable = enable; + return noErr; +} + +OSStatus +SSLGetAllowAnonymousCiphers( + SSLContextRef ctx, + Boolean *enable) +{ + if((ctx == NULL) || (enable == NULL)) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + return badReqErr; + } + *enable = ctx->anonCipherEnable; + return noErr; +} + +/* + * Override the default session cache timeout for a cache entry created for + * the current session. + */ +OSStatus +SSLSetSessionCacheTimeout( + SSLContextRef ctx, + uint32_t timeoutInSeconds) +{ + if(ctx == NULL) { + return paramErr; + } + ctx->sessionCacheTimeout = timeoutInSeconds; + return noErr; +} + +/* + * Register a callback for obtaining the master_secret when performing + * PAC-based session resumption. + */ +OSStatus +SSLInternalSetMasterSecretFunction( + SSLContextRef ctx, + SSLInternalMasterSecretFunction mFunc, + const void *arg) /* opaque to SecureTransport; app-specific */ +{ + if(ctx == NULL) { + return paramErr; + } + ctx->masterSecretCallback = mFunc; + ctx->masterSecretArg = arg; + return noErr; +} + +/* + * Provide an opaque SessionTicket for use in PAC-based session + * resumption. Client side only. The provided ticket is sent in + * the ClientHello message as a SessionTicket extension. + * + * We won't reject this on the server side, but server-side support + * for PAC-based session resumption is currently enabled for + * Development builds only. To fully support this for server side, + * besides the rudimentary support that's here for Development builds, + * we'd need a getter for the session ticket, so the app code can + * access the SessionTicket when its SSLInternalMasterSecretFunction + * callback is called. + */ +OSStatus SSLInternalSetSessionTicket( + SSLContextRef ctx, + const void *ticket, + size_t ticketLength) +{ + if(ctx == NULL) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + if(ticketLength > 0xffff) { + /* extension data encoded with a 2-byte length! */ + return paramErr; + } + SSLFreeBuffer(&ctx->sessionTicket, NULL); + return SSLCopyBufferFromData(ticket, ticketLength, &ctx->sessionTicket); +} + +/* + * ECDSA curve accessors. + */ + +/* + * Obtain the SSL_ECDSA_NamedCurve negotiated during a handshake. + * Returns paramErr if no ECDH-related ciphersuite was negotiated. + */ +OSStatus SSLGetNegotiatedCurve( + SSLContextRef ctx, + SSL_ECDSA_NamedCurve *namedCurve) /* RETURNED */ +{ + if((ctx == NULL) || (namedCurve == NULL)) { + return paramErr; + } + if(ctx->ecdhPeerCurve == SSL_Curve_None) { + return paramErr; + } + *namedCurve = ctx->ecdhPeerCurve; + return noErr; +} + +/* + * Obtain the number of currently enabled SSL_ECDSA_NamedCurves. + */ +OSStatus SSLGetNumberOfECDSACurves( + SSLContextRef ctx, + unsigned *numCurves) /* RETURNED */ +{ + if((ctx == NULL) || (numCurves == NULL)) { + return paramErr; + } + *numCurves = ctx->ecdhNumCurves; + return noErr; +} + +/* + * Obtain the ordered list of currently enabled SSL_ECDSA_NamedCurves. + */ +OSStatus SSLGetECDSACurves( + SSLContextRef ctx, + SSL_ECDSA_NamedCurve *namedCurves, /* RETURNED */ + unsigned *numCurves) /* IN/OUT */ +{ + if((ctx == NULL) || (namedCurves == NULL) || (numCurves == NULL)) { + return paramErr; + } + if(*numCurves < ctx->ecdhNumCurves) { + return paramErr; + } + memmove(namedCurves, ctx->ecdhCurves, + (ctx->ecdhNumCurves * sizeof(SSL_ECDSA_NamedCurve))); + *numCurves = ctx->ecdhNumCurves; + return noErr; +} + +/* + * Specify ordered list of allowable named curves. + */ +OSStatus SSLSetECDSACurves( + SSLContextRef ctx, + const SSL_ECDSA_NamedCurve *namedCurves, + unsigned numCurves) +{ + if((ctx == NULL) || (namedCurves == NULL) || (numCurves == 0)) { + return paramErr; + } + if(numCurves > SSL_ECDSA_NUM_CURVES) { + return paramErr; + } + if(sslIsSessionActive(ctx)) { + /* can't do this with an active session */ + return badReqErr; + } + memmove(ctx->ecdhCurves, namedCurves, (numCurves * sizeof(SSL_ECDSA_NamedCurve))); + ctx->ecdhNumCurves = numCurves; + return noErr; +} + +/* + * Obtain the number of client authentication mechanisms specified by + * the server in its Certificate Request message. + * Returns paramErr if server hasn't sent a Certificate Request message + * (i.e., client certificate state is kSSLClientCertNone). + */ +OSStatus SSLGetNumberOfClientAuthTypes( + SSLContextRef ctx, + unsigned *numTypes) +{ + if((ctx == NULL) || (ctx->clientCertState == kSSLClientCertNone)) { + return paramErr; + } + *numTypes = ctx->numAuthTypes; + return noErr; +} + +/* + * Obtain the client authentication mechanisms specified by + * the server in its Certificate Request message. + * Caller allocates returned array and specifies its size (in + * SSLClientAuthenticationTypes) in *numType on entry; *numTypes + * is the actual size of the returned array on successful return. + */ +OSStatus SSLGetClientAuthTypes( + SSLContextRef ctx, + SSLClientAuthenticationType *authTypes, /* RETURNED */ + unsigned *numTypes) /* IN/OUT */ +{ + if((ctx == NULL) || (ctx->clientCertState == kSSLClientCertNone)) { + return paramErr; + } + memmove(authTypes, ctx->clientAuthTypes, + ctx->numAuthTypes * sizeof(SSLClientAuthenticationType)); + *numTypes = ctx->numAuthTypes; + return noErr; +} + +/* + * Obtain the SSLClientAuthenticationType actually performed. + * Only valid if client certificate state is kSSLClientCertSent + * or kSSLClientCertRejected; returns paramErr otherwise. + */ +OSStatus SSLGetNegotiatedClientAuthType( + SSLContextRef ctx, + SSLClientAuthenticationType *authType) /* RETURNED */ +{ + if(ctx == NULL) { + return paramErr; + } + *authType = ctx->negAuthType; + return noErr; +} + +/* + * Update the negotiated client authentication type. + * This function may be called at any time; however, note that + * the negotiated authentication type will be SSLClientAuthNone + * until both of the following have taken place (in either order): + * - a CertificateRequest message from the server has been processed + * - a client certificate has been specified + * As such, this function (only) needs to be called from (both) + * SSLProcessCertificateRequest and SSLSetCertificate. + */ +OSStatus SSLUpdateNegotiatedClientAuthType( + SSLContextRef ctx) +{ + if(ctx == NULL) { + return paramErr; + } + /* + * See if we have a signing cert that matches one of the + * allowed auth types. The x509Requested flag indicates "we + * have a cert that we think the server will accept". + */ + ctx->x509Requested = 0; + ctx->negAuthType = SSLClientAuthNone; + if(ctx->signingPrivKeyRef != NULL) { + CFIndex ourKeyAlg = sslPubKeyGetAlgorithmID(ctx->signingPubKey); + unsigned i; + for(i=0; inumAuthTypes; i++) { + switch(ctx->clientAuthTypes[i]) { + case SSLClientAuth_RSASign: + if(ourKeyAlg == kSecRSAAlgorithmID) { + ctx->x509Requested = 1; + ctx->negAuthType = SSLClientAuth_RSASign; + } + break; + #if SSL_ENABLE_ECDSA_SIGN_AUTH + case SSLClientAuth_ECDSASign: + #endif + #if SSL_ENABLE_ECDSA_FIXED_ECDH_AUTH + case SSLClientAuth_ECDSAFixedECDH: + #endif + if((ourKeyAlg == kSecECDSAAlgorithmID) && + (ctx->ourSignerAlg == kSecECDSAAlgorithmID)) { + ctx->x509Requested = 1; + ctx->negAuthType = ctx->clientAuthTypes[i]; + } + break; + #if SSL_ENABLE_RSA_FIXED_ECDH_AUTH + case SSLClientAuth_RSAFixedECDH: + /* Odd case, we differ from our signer */ + if((ourKeyAlg == kSecECDSAAlgorithmID) && + (ctx->ourSignerAlg == kSecRSAAlgorithmID)) { + ctx->x509Requested = 1; + ctx->negAuthType = SSLClientAuth_RSAFixedECDH; + } + break; + #endif + default: + /* None others supported */ + break; + } + if(ctx->x509Requested) { + sslLogNegotiateDebug("===CHOOSING authType %d", (int)ctx->negAuthType); + break; + } + } /* parsing authTypes */ + } /* we have a signing key */ + + return noErr; +} + +OSStatus SSLGetNumberOfSignatureAlgorithms( + SSLContextRef ctx, + unsigned *numSigAlgs) +{ + if((ctx == NULL) || (ctx->clientCertState == kSSLClientCertNone)) { + return paramErr; + } + *numSigAlgs = ctx->numServerSigAlgs; + return noErr; +} + +OSStatus SSLGetSignatureAlgorithms( + SSLContextRef ctx, + SSLSignatureAndHashAlgorithm *sigAlgs, /* RETURNED */ + unsigned *numSigAlgs) /* IN/OUT */ +{ + if((ctx == NULL) || (ctx->clientCertState == kSSLClientCertNone)) { + return paramErr; + } + memmove(sigAlgs, ctx->serverSigAlgs, + ctx->numServerSigAlgs * sizeof(SSLSignatureAndHashAlgorithm)); + *numSigAlgs = ctx->numServerSigAlgs; + return noErr; +}