sslSession.c
1 /* 2 * Copyright (c) 2000-2001,2005-2008,2010-2012 Apple Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24 25 #include "tls_handshake_priv.h" 26 #include "sslSession.h" 27 #include "sslMemory.h" 28 #include "sslUtils.h" 29 #include "sslDebug.h" 30 #include "sslCipherSpecs.h" 31 #include "sslAlertMessage.h" 32 #include "sslHandshake_priv.h" 33 34 #include <assert.h> 35 #include <string.h> 36 #include <stddef.h> 37 38 typedef struct 39 { 40 size_t sessionIDLen; 41 uint8_t sessionID[32]; 42 tls_protocol_version negProtocolVersion; /* negotiated version */ 43 tls_protocol_version reqProtocolVersion; /* version requested by client */ 44 uint16_t cipherSuite; 45 uint16_t padding; /* so remainder is word aligned */ 46 uint8_t masterSecret[48]; 47 size_t ticketLen; 48 size_t ocspResponseLen; 49 size_t sctListLen; 50 size_t certListLen; 51 bool sessExtMSSet; 52 uint8_t data[0]; /* Actually, variable length */ 53 /* data contain the session ticket (if any), followed by stappled ocsp response (if any), 54 sct list (if any), and finally certs list */ 55 } ResumableSession; 56 57 /* Special fixed sessionID when using a session ticket */ 58 #define SESSION_TICKET_ID "SESSION-TICKET" 59 60 /* 61 * Given a sessionData blob, perform sanity check 62 */ 63 static bool 64 SSLSessionDataCheck(const tls_buffer sessionData) 65 { 66 ResumableSession *session; 67 if(sessionData.length < sizeof(ResumableSession)) 68 return false; 69 70 session = (ResumableSession *)sessionData.data; 71 72 return (sessionData.length == (sizeof(ResumableSession) + session->ticketLen + session->ocspResponseLen + session->sctListLen + session->certListLen)); 73 } 74 /* 75 * Cook up a (private) resumable session blob, based on the 76 * specified ctx, store it with ctx->peerID (client) or ctx->sessionID (server) as the key. 77 */ 78 int 79 SSLAddSessionData(const tls_handshake_t ctx) 80 { int err; 81 size_t sessionDataLen; 82 tls_buffer sessionData; 83 ResumableSession *session; 84 size_t sctListLen; 85 size_t certListLen; 86 uint8_t *p; 87 88 /* If we don't have session ID or a ticket, we can't store a session */ 89 if (!ctx->sessionID.data && !ctx->sessionTicket.data) 90 return errSSLSessionNotFound; 91 92 sctListLen = SSLEncodedBufferListSize(ctx->sct_list, 2); 93 certListLen = SSLEncodedBufferListSize((tls_buffer_list_t *)ctx->peerCert, 3); 94 95 sessionDataLen = sizeof(ResumableSession) + ctx->sessionTicket.length 96 + ctx->ocsp_response.length + sctListLen + certListLen; 97 98 if ((err = SSLAllocBuffer(&sessionData, sessionDataLen))) 99 return err; 100 101 session = (ResumableSession*)sessionData.data; 102 103 if(ctx->sessionID.data==NULL) { 104 session->sessionIDLen = strlen(SESSION_TICKET_ID); 105 memcpy(session->sessionID, SESSION_TICKET_ID, session->sessionIDLen); 106 } else { 107 session->sessionIDLen = ctx->sessionID.length; 108 memcpy(session->sessionID, ctx->sessionID.data, session->sessionIDLen); 109 } 110 session->negProtocolVersion = ctx->negProtocolVersion; 111 session->reqProtocolVersion = ctx->clientReqProtocol; 112 session->cipherSuite = ctx->selectedCipher; 113 memcpy(session->masterSecret, ctx->masterSecret, 48); 114 session->ticketLen = ctx->sessionTicket.length; 115 session->ocspResponseLen = ctx->ocsp_response.length; 116 session->sctListLen = sctListLen; 117 session->certListLen = certListLen; 118 session->padding = 0; 119 p = session->data; 120 121 memcpy(p, ctx->sessionTicket.data, ctx->sessionTicket.length); p += ctx->sessionTicket.length; 122 memcpy(p, ctx->ocsp_response.data, ctx->ocsp_response.length); p += ctx->ocsp_response.length; 123 p = SSLEncodeBufferList(ctx->sct_list, 2, p); 124 p = SSLEncodeBufferList((tls_buffer_list_t *)ctx->peerCert, 3, p); 125 126 if(!SSLSessionDataCheck(sessionData)) { 127 SSLFreeBuffer(&sessionData); 128 return errSSLInternal; 129 } 130 131 if (ctx->extMSEnabled && ctx->extMSReceived) 132 session->sessExtMSSet = true; 133 else 134 session->sessExtMSSet = false; 135 136 if(ctx->isServer) 137 err = ctx->callbacks->save_session_data(ctx->callback_ctx, ctx->sessionID, sessionData); 138 else 139 err = ctx->callbacks->save_session_data(ctx->callback_ctx, ctx->peerID, sessionData); 140 141 142 SSLFreeBuffer(&sessionData); 143 144 return err; 145 } 146 147 int 148 SSLDeleteSessionData(const tls_handshake_t ctx) 149 { int err; 150 151 if (ctx->sessionID.data == 0) 152 return errSSLSessionNotFound; 153 154 err = ctx->callbacks->delete_session_data(ctx->callback_ctx, ctx->sessionID); 155 return err; 156 } 157 158 /* 159 * Given a sessionData blob, obtain the associated sessionTicket (if Any). 160 */ 161 int 162 SSLRetrieveSessionTicket( 163 const tls_buffer sessionData, 164 tls_buffer *ticket) 165 { 166 ResumableSession *session; 167 168 if(!SSLSessionDataCheck(sessionData)) 169 return errSSLInternal; 170 171 session = (ResumableSession*) sessionData.data; 172 ticket->data = session->data; 173 ticket->length = session->ticketLen; 174 return errSSLSuccess; 175 } 176 177 /* 178 * Given a sessionData blob, obtain the associated sessionID (NOT the key...). 179 */ 180 int 181 SSLRetrieveSessionID( 182 const tls_buffer sessionData, 183 tls_buffer *identifier) 184 { 185 ResumableSession *session; 186 187 if(!SSLSessionDataCheck(sessionData)) 188 return errSSLInternal; 189 190 session = (ResumableSession*) sessionData.data; 191 identifier->data = session->sessionID; 192 identifier->length = session->sessionIDLen; 193 return errSSLSuccess; 194 } 195 196 /* 197 * Validate session data on the server side, after receiving ClientHello. 198 * If this fails, the server will attempt a full handshake. 199 */ 200 int SSLServerValidateSessionData(const tls_buffer sessionData, tls_handshake_t ctx) 201 { 202 ResumableSession *session = (ResumableSession *)sessionData.data; 203 204 if(!SSLSessionDataCheck(sessionData)) 205 return errSSLInternal; 206 207 /* 208 Currently, the session cache is looked up by sessionID on the server side, 209 so these two checks are mostly redundant and act as sanity check, in case 210 the session cache callbacks are badly implemented. 211 It would also be possible to lookup by more than sessionID, eg: you could 212 lookup by sessionID + protocol version, to handle session resumption 213 with in various version fallback cases. 214 */ 215 216 require(session->sessionIDLen == ctx->proposedSessionID.length, out); 217 require(memcmp(session->sessionID, ctx->proposedSessionID.data, ctx->proposedSessionID.length) == 0, out); 218 219 /* 220 If for some reason a session was cached with a different protocol version, 221 then the server will fallback to a full handshake. We could accept to resume the session with 222 the cached version, but we prefer to negotiate the best possible version instead. 223 */ 224 require(session->negProtocolVersion == ctx->negProtocolVersion, out); 225 226 /* 227 We also check that the session cipherSuite is in the list of enabled ciphersuites, 228 and also in the list of ciphersuites requested by the client. 229 */ 230 231 require(cipherSuiteInSet(session->cipherSuite, ctx->enabledCipherSuites, ctx->numEnabledCipherSuites), out); 232 require(cipherSuiteInSet(session->cipherSuite, ctx->requestedCipherSuites, ctx->numRequestedCipherSuites), out); 233 234 if (session->sessExtMSSet) { 235 if (!ctx->extMSReceived) { 236 SSLFatalSessionAlert(SSL_AlertHandshakeFail, ctx); 237 return errSSLFatalAlert; 238 } 239 } else { 240 if (ctx->extMSReceived) { 241 goto out; 242 } 243 } 244 ctx->selectedCipher = session->cipherSuite; 245 InitCipherSpecParams(ctx); 246 247 return 0; 248 249 out: 250 return errSSLSessionNotFound; 251 } 252 253 /* 254 * Validate session data on the client side, before sending the ClientHello. 255 * If this fails, the client will no attempt resumption. 256 */ 257 int SSLClientValidateSessionDataBefore(const tls_buffer sessionData, tls_handshake_t ctx) 258 { 259 ResumableSession *session = (ResumableSession *)sessionData.data; 260 261 if(!SSLSessionDataCheck(sessionData)) 262 return errSSLInternal; 263 264 /* 265 If the current requested version is higher than the session one, 266 we do not re-use this session (see: rdar://23329369) 267 */ 268 require(ctx->maxProtocolVersion <= session->reqProtocolVersion, out); 269 270 /* 271 Make sure that the session version is within our enabled versions. 272 */ 273 require(session->negProtocolVersion <= ctx->maxProtocolVersion, out); 274 require(session->negProtocolVersion >= ctx->minProtocolVersion, out); 275 276 /* 277 Make sure that the session ciphersuite is within our enabled ciphers 278 */ 279 require(cipherSuiteInSet(session->cipherSuite, ctx->enabledCipherSuites, ctx->numEnabledCipherSuites), out); 280 281 return 0; 282 out: 283 return errSSLSessionNotFound; 284 } 285 286 /* 287 * Validate session data on the client side, after receiving the ServerHello. 288 * If this fails, the client will abort the connection. 289 */ 290 int SSLClientValidateSessionDataAfter(const tls_buffer sessionData, tls_handshake_t ctx) 291 { 292 ResumableSession *session = (ResumableSession *)sessionData.data; 293 294 if(!SSLSessionDataCheck(sessionData)) 295 return errSSLInternal; 296 297 /* Make sure that the session version and server version match. */ 298 require(session->negProtocolVersion == ctx->negProtocolVersion, out); 299 /* Make sure that the session ciphersuite and server ciphersuite match. */ 300 require(session->cipherSuite == ctx->selectedCipher, out); 301 require(session->sessExtMSSet == ctx->extMSReceived, out); 302 return 0; 303 304 out: 305 return errSSLProtocol; 306 } 307 308 309 /* 310 * Retrieve session state from specified sessionData blob, install into 311 * ctx. Presumably, ctx->sessionID and 312 * ctx->negProtocolVersion are already init'd (from the above two functions). 313 */ 314 int 315 SSLInstallSessionFromData(const tls_buffer sessionData, tls_handshake_t ctx) 316 { int err; 317 ResumableSession *session; 318 uint8_t *p; 319 320 if(!SSLSessionDataCheck(sessionData)) 321 return errSSLInternal; 322 323 session = (ResumableSession*)sessionData.data; 324 325 /* 326 * selectedCipher and negProtocolVersion should already be validated. 327 */ 328 assert(ctx->selectedCipher == session->cipherSuite); 329 assert(ctx->negProtocolVersion == session->negProtocolVersion); 330 331 memcpy(ctx->masterSecret, session->masterSecret, 48); 332 p = session->data + session->ticketLen; 333 334 // Get the stappled ocsp_response (if any) 335 SSLFreeBuffer(&ctx->ocsp_response); 336 ctx->ocsp_response_received = false; 337 if(session->ocspResponseLen) { 338 ctx->ocsp_response_received = true; 339 SSLCopyBufferFromData(p, session->ocspResponseLen, &ctx->ocsp_response); 340 } 341 p += session->ocspResponseLen; 342 343 // Get the SCT list (if any) 344 tls_free_buffer_list(ctx->sct_list); 345 ctx->sct_list = NULL; 346 if(session->sctListLen) { 347 if((err=SSLDecodeBufferList(p, session->sctListLen, 2, &ctx->sct_list))) { 348 return err; 349 } 350 } 351 p += session->sctListLen; 352 353 // Get the certs 354 SSLFreeCertificates(ctx->peerCert); 355 ctx->peerCert = NULL; 356 if(session->certListLen) { 357 if((err=SSLDecodeBufferList(p, session->certListLen, 3, (tls_buffer_list_t **)&ctx->peerCert))) { 358 return err; 359 } 360 } 361 362 return errSSLSuccess; 363 }