]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_ssl/lib/sslSession.c
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_ssl / lib / sslSession.c
diff --git a/libsecurity_ssl/lib/sslSession.c b/libsecurity_ssl/lib/sslSession.c
new file mode 100644 (file)
index 0000000..49cdafe
--- /dev/null
@@ -0,0 +1,359 @@
+/*
+ * Copyright (c) 2000-2001,2005-2008,2010-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@
+ */
+
+#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 <assert.h>
+#include <string.h>
+#include <stddef.h>
+#include <Security/SecCertificate.h>
+#include <Security/SecCertificatePriv.h>
+#include <Security/SecInternal.h>
+
+typedef struct
+{   size_t              sessionIDLen;
+    UInt8               sessionID[32];
+    SSLProtocolVersion  protocolVersion;
+    UInt16              cipherSuite;
+       UInt16                          padding;        /* so remainder is word aligned */
+    UInt8               masterSecret[48];
+    size_t              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;
+       size_t              sessionIDLen;
+       SSLBuffer           sessionID;
+       ResumableSession    *session;
+       size_t              certCount;
+#ifdef USE_SSLCERTIFICATE
+       SSLCertificate      *cert;
+#else
+       CFArrayRef                      certChain;
+       size_t                          ix;
+#endif
+       uint8_t             *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);
+#ifdef USE_SSLCERTIFICATE
+       cert = ctx->peerCert;
+       certCount = 0;
+       while (cert)
+       {   ++certCount;
+               sessionIDLen += 4 + cert->derCert.length;
+               cert = cert->next;
+       }
+#else
+       certChain = ctx->peerCert;
+       certCount = certChain ? CFArrayGetCount(certChain) : 0;
+       for (ix = 0; ix < certCount; ++ix) {
+               SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certChain, ix);
+               #if SSL_DEBUG
+               sslDebugLog("SSLAddSessionData: got cert %d of %d\n", ix+1, certCount);
+               if (!cert || CFGetTypeID(cert) != SecCertificateGetTypeID()) {
+                       sslErrorLog("SSLAddSessionData: non-cert in peerCert array!\n");
+               }
+               #endif
+               sessionIDLen += 4 + (size_t)SecCertificateGetLength(cert);
+       }
+#endif
+
+       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;
+
+#ifdef USE_SSLCERTIFICATE
+       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;
+       }
+#else
+       for (ix = 0; ix < certCount; ++ix) {
+               SecCertificateRef certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certChain, ix);
+               size_t certLength = (size_t)SecCertificateGetLength(certRef);
+               const uint8_t *certBytes = SecCertificateGetBytePtr(certRef);
+
+               #if SSL_DEBUG
+               /* print cert name when debugging; leave disabled otherwise */
+               CFStringRef certName = NULL;
+               OSStatus status = SecCertificateInferLabel(certRef, &certName);
+               char buf[1024];
+               if (!certName || !CFStringGetCString(certName, buf, 1024-1, kCFStringEncodingUTF8)) { buf[0]=0; }
+               sslDebugLog("SSLAddSessionData: flattening \"%s\" (%ld bytes)\n", buf, certLength);
+               CFReleaseSafe(certName);
+               #endif
+
+               if (!certBytes || !certLength) {
+                       sslErrorLog("SSLAddSessionData: invalid certificate at index %d of %d (length=%ld, data=%p)\n",
+                                       ix, certCount-1, certLength, (const uintptr_t)certBytes);
+                       err = paramErr; /* if we have a bad cert, don't add session to cache */
+               }
+               else {
+                       certDest = SSLEncodeSize(certDest, certLength, 4);
+                       memcpy(certDest, certBytes, certLength);
+                       certDest += certLength;
+               }
+       }
+#endif
+
+       if (!err) {
+               err = sslAddSession(ctx->peerID, sessionID, ctx->sessionCacheTimeout);
+       }
+       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_t             *storedCertProgress;
+#ifdef USE_SSLCERTIFICATE
+    SSLCertificate             *cert;
+    SSLCertificate      *lastCert = NULL;
+#else
+    SecCertificateRef   cert;
+    CFMutableArrayRef  certChain = NULL;
+#endif
+    size_t              certCount;
+    size_t              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 == kSSLClientSide) {
+                       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);
+
+    storedCertProgress = session->certs;
+    certCount = session->certCount;
+
+    while (certCount--)
+    {
+#ifdef USE_SSLCERTIFICATE
+               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;
+#else
+        certLen = SSLDecodeInt(storedCertProgress, 4);
+        storedCertProgress += 4;
+               cert = SecCertificateCreateWithBytes(NULL, storedCertProgress, certLen);
+               #if SSL_DEBUG
+               sslDebugLog("SSLInstallSessionFromData: creating cert with bytes=%p len=%lu\n",
+                       (uintptr_t)storedCertProgress, certLen);
+               if (!cert || CFGetTypeID(cert) != SecCertificateGetTypeID()) {
+                       sslErrorLog("SSLInstallSessionFromData: SecCertificateCreateWithBytes failed\n");
+               }
+               #endif
+               if(cert == NULL) {
+                       return memFullErr;
+               }
+        storedCertProgress += certLen;
+               /* @@@ This is almost the same code as in sslCert.c: SSLProcessCertificate() */
+               if (!certChain) {
+                       certChain = CFArrayCreateMutable(kCFAllocatorDefault,
+                               session->certCount, &kCFTypeArrayCallBacks);
+            if (!certChain) {
+                               CFRelease(cert);
+                               return memFullErr;
+                       }
+                       ctx->peerCert = certChain;
+               }
+
+               CFArrayAppendValue(certChain, cert);
+               CFRelease(cert);
+#endif
+    }
+
+    return noErr;
+}