/* * 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. */ #include "ssl.h" #include "sslContext.h" #include "sslSession.h" #include "sslMemory.h" #include "sslUtils.h" #include "sslDebug.h" #include "cipherSpecs.h" #include "appleSession.h" #include #include #include typedef struct { int sessionIDLen; UInt8 sessionID[32]; SSLProtocolVersion protocolVersion; UInt16 cipherSuite; UInt16 padding; /* so remainder is word aligned */ UInt8 masterSecret[48]; int certCount; UInt8 certs[1]; /* Actually, variable length */ } ResumableSession; /* * Cook up a (private) resumable session blob, based on the * specified ctx, store it with ctx->peerID as the key. * NOTE: This is contrary to the SSL v3 spec, which claims that * servers store resumable sessions using ctx->sessionID as the key. * I don' think this is an issue...is it? */ OSStatus SSLAddSessionData(const SSLContext *ctx) { OSStatus err; uint32 sessionIDLen; SSLBuffer sessionID; ResumableSession *session; int certCount; SSLCertificate *cert; uint8 *certDest; /* If we don't know who the peer is, we can't store a session */ if (ctx->peerID.data == 0) return errSSLSessionNotFound; sessionIDLen = offsetof(ResumableSession, certs); cert = ctx->peerCert; certCount = 0; while (cert) { ++certCount; sessionIDLen += 4 + cert->derCert.length; cert = cert->next; } if ((err = SSLAllocBuffer(sessionID, sessionIDLen, ctx)) != 0) return err; session = (ResumableSession*)sessionID.data; session->sessionIDLen = ctx->sessionID.length; memcpy(session->sessionID, ctx->sessionID.data, session->sessionIDLen); session->protocolVersion = ctx->negProtocolVersion; session->cipherSuite = ctx->selectedCipher; memcpy(session->masterSecret, ctx->masterSecret, 48); session->certCount = certCount; session->padding = 0; certDest = session->certs; cert = ctx->peerCert; while (cert) { certDest = SSLEncodeInt(certDest, cert->derCert.length, 4); memcpy(certDest, cert->derCert.data, cert->derCert.length); certDest += cert->derCert.length; cert = cert->next; } err = sslAddSession(ctx->peerID, sessionID); SSLFreeBuffer(sessionID, ctx); return err; } /* * Retrieve resumable session data, from key ctx->peerID. */ OSStatus SSLGetSessionData(SSLBuffer *sessionData, const SSLContext *ctx) { OSStatus err; if (ctx->peerID.data == 0) return errSSLSessionNotFound; sessionData->data = 0; err = sslGetSession(ctx->peerID, sessionData); if (sessionData->data == 0) return errSSLSessionNotFound; return err; } OSStatus SSLDeleteSessionData(const SSLContext *ctx) { OSStatus err; if (ctx->peerID.data == 0) return errSSLSessionNotFound; err = sslDeleteSession(ctx->peerID); return err; } /* * Given a sessionData blob, obtain the associated sessionID (NOT the key...). */ OSStatus SSLRetrieveSessionID( const SSLBuffer sessionData, SSLBuffer *identifier, const SSLContext *ctx) { OSStatus err; ResumableSession *session; session = (ResumableSession*) sessionData.data; if ((err = SSLAllocBuffer(*identifier, session->sessionIDLen, ctx)) != 0) return err; memcpy(identifier->data, session->sessionID, session->sessionIDLen); return noErr; } /* * Obtain the protocol version associated with a specified resumable session blob. */ OSStatus SSLRetrieveSessionProtocolVersion( const SSLBuffer sessionData, SSLProtocolVersion *version, const SSLContext *ctx) { ResumableSession *session; session = (ResumableSession*) sessionData.data; *version = session->protocolVersion; return noErr; } /* * Retrieve session state from specified sessionData blob, install into * ctx. Presumably, ctx->sessionID and * ctx->negProtocolVersion are already init'd (from the above two functions). */ /* * Netscape Enterprise Server is known to change cipherspecs upon session resumption. * For example, connecting to cdnow.com with all ciphersuites enabled results in * CipherSuite 4 (SSL_RSA_WITH_RC4_128_MD5) being selected on the first session, * and CipherSuite 10 (SSL_RSA_WITH_3DES_EDE_CBC_SHA) being selected on subsequent * sessions. This is contrary to the SSL3.0 spec, sesion 7.6.1.3, describing the * Server Hello message. * * This anomaly does not occur if only RC4 ciphers are enabled in the Client Hello * message. It also does not happen in SSL V2. */ #define ALLOW_CIPHERSPEC_CHANGE 1 OSStatus SSLInstallSessionFromData(const SSLBuffer sessionData, SSLContext *ctx) { OSStatus err; ResumableSession *session; uint8 *storedCertProgress; SSLCertificate *cert, *lastCert; int certCount; uint32 certLen; session = (ResumableSession*)sessionData.data; /* * For SSLv3 and TLSv1, we know that selectedCipher has already been specified in * SSLProcessServerHello(). An SSLv2 server hello message with a session * ID hit contains no CipherKind field so we set it here. */ if(ctx->negProtocolVersion == SSL_Version_2_0) { if(ctx->protocolSide == SSL_ClientSide) { assert(ctx->selectedCipher == 0); ctx->selectedCipher = session->cipherSuite; } else { /* * Else...what if they don't match? Could never happen, right? * Wouldn't that mean the client is trying to switch ciphers on us? */ if(ctx->selectedCipher != session->cipherSuite) { sslErrorLog("+++SSL2: CipherSpec change from %d to %d on session " "resume\n", session->cipherSuite, ctx->selectedCipher); return errSSLProtocol; } } } else { assert(ctx->selectedCipher != 0); if(ctx->selectedCipher != session->cipherSuite) { #if ALLOW_CIPHERSPEC_CHANGE sslErrorLog("+++WARNING: CipherSpec change from %d to %d " "on session resume\n", session->cipherSuite, ctx->selectedCipher); #else sslErrorLog("+++SSL: CipherSpec change from %d to %d on session resume\n", session->cipherSuite, ctx->selectedCipher); return errSSLProtocol; #endif } } if ((err = FindCipherSpec(ctx)) != 0) { return err; } memcpy(ctx->masterSecret, session->masterSecret, 48); lastCert = 0; storedCertProgress = session->certs; certCount = session->certCount; while (certCount--) { cert = (SSLCertificate *)sslMalloc(sizeof(SSLCertificate)); if(cert == NULL) { return memFullErr; } cert->next = 0; certLen = SSLDecodeInt(storedCertProgress, 4); storedCertProgress += 4; if ((err = SSLAllocBuffer(cert->derCert, certLen, ctx)) != 0) { sslFree(cert); return err; } memcpy(cert->derCert.data, storedCertProgress, certLen); storedCertProgress += certLen; if (lastCert == 0) ctx->peerCert = cert; else lastCert->next = cert; lastCert = cert; } return noErr; }