/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file implements the CLIENT Session ID cache. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "cert.h" #include "pk11pub.h" #include "secitem.h" #include "ssl.h" #include "nss.h" #include "sslimpl.h" #include "sslproto.h" #include "nssilock.h" #include "sslencode.h" #if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) #include #endif static sslSessionID *cache = NULL; static PZLock *cacheLock = NULL; /* sids can be in one of 5 states: * * never_cached, created, but not yet put into cache. * in_client_cache, in the client cache's linked list. * in_server_cache, entry came from the server's cache file. * invalid_cache has been removed from the cache. * in_external_cache sid comes from an external cache. */ #define LOCK_CACHE lock_cache() #define UNLOCK_CACHE PZ_Unlock(cacheLock) static SECStatus ssl_InitClientSessionCacheLock(void) { cacheLock = PZ_NewLock(nssILockCache); return cacheLock ? SECSuccess : SECFailure; } static SECStatus ssl_FreeClientSessionCacheLock(void) { if (cacheLock) { PZ_DestroyLock(cacheLock); cacheLock = NULL; return SECSuccess; } PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return SECFailure; } static PRBool LocksInitializedEarly = PR_FALSE; static SECStatus FreeSessionCacheLocks() { SECStatus rv1, rv2; rv1 = ssl_FreeSymWrapKeysLock(); rv2 = ssl_FreeClientSessionCacheLock(); if ((SECSuccess == rv1) && (SECSuccess == rv2)) { return SECSuccess; } return SECFailure; } static SECStatus InitSessionCacheLocks(void) { SECStatus rv1, rv2; PRErrorCode rc; rv1 = ssl_InitSymWrapKeysLock(); rv2 = ssl_InitClientSessionCacheLock(); if ((SECSuccess == rv1) && (SECSuccess == rv2)) { return SECSuccess; } rc = PORT_GetError(); FreeSessionCacheLocks(); PORT_SetError(rc); return SECFailure; } /* free the session cache locks if they were initialized early */ SECStatus ssl_FreeSessionCacheLocks() { PORT_Assert(PR_TRUE == LocksInitializedEarly); if (!LocksInitializedEarly) { PORT_SetError(SEC_ERROR_NOT_INITIALIZED); return SECFailure; } FreeSessionCacheLocks(); LocksInitializedEarly = PR_FALSE; return SECSuccess; } static PRCallOnceType lockOnce; /* free the session cache locks if they were initialized lazily */ static SECStatus ssl_ShutdownLocks(void *appData, void *nssData) { PORT_Assert(PR_FALSE == LocksInitializedEarly); if (LocksInitializedEarly) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } FreeSessionCacheLocks(); memset(&lockOnce, 0, sizeof(lockOnce)); return SECSuccess; } static PRStatus initSessionCacheLocksLazily(void) { SECStatus rv = InitSessionCacheLocks(); if (SECSuccess != rv) { return PR_FAILURE; } rv = NSS_RegisterShutdown(ssl_ShutdownLocks, NULL); PORT_Assert(SECSuccess == rv); if (SECSuccess != rv) { return PR_FAILURE; } return PR_SUCCESS; } /* lazyInit means that the call is not happening during a 1-time * initialization function, but rather during dynamic, lazy initialization */ SECStatus ssl_InitSessionCacheLocks(PRBool lazyInit) { if (LocksInitializedEarly) { return SECSuccess; } if (lazyInit) { return (PR_SUCCESS == PR_CallOnce(&lockOnce, initSessionCacheLocksLazily)) ? SECSuccess : SECFailure; } if (SECSuccess == InitSessionCacheLocks()) { LocksInitializedEarly = PR_TRUE; return SECSuccess; } return SECFailure; } static void lock_cache(void) { ssl_InitSessionCacheLocks(PR_TRUE); PZ_Lock(cacheLock); } /* BEWARE: This function gets called for both client and server SIDs !! * If the unreferenced sid is not in the cache, Free sid and its contents. */ void ssl_DestroySID(sslSessionID *sid, PRBool freeIt) { SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached)); PORT_Assert(sid->references == 0); PORT_Assert(sid->cached != in_client_cache); if (sid->u.ssl3.locked.sessionTicket.ticket.data) { SECITEM_FreeItem(&sid->u.ssl3.locked.sessionTicket.ticket, PR_FALSE); } if (sid->u.ssl3.srvName.data) { SECITEM_FreeItem(&sid->u.ssl3.srvName, PR_FALSE); } if (sid->u.ssl3.signedCertTimestamps.data) { SECITEM_FreeItem(&sid->u.ssl3.signedCertTimestamps, PR_FALSE); } if (sid->u.ssl3.lock) { PR_DestroyRWLock(sid->u.ssl3.lock); } PORT_Free((void *)sid->peerID); PORT_Free((void *)sid->urlSvrName); if (sid->peerCert) { CERT_DestroyCertificate(sid->peerCert); } if (sid->peerCertStatus.items) { SECITEM_FreeArray(&sid->peerCertStatus, PR_FALSE); } if (sid->localCert) { CERT_DestroyCertificate(sid->localCert); } SECITEM_FreeItem(&sid->u.ssl3.alpnSelection, PR_FALSE); if (freeIt) { PORT_ZFree(sid, sizeof(sslSessionID)); } } /* BEWARE: This function gets called for both client and server SIDs !! * Decrement reference count, and * free sid if ref count is zero, and sid is not in the cache. * Does NOT remove from the cache first. * If the sid is still in the cache, it is left there until next time * the cache list is traversed. */ static void ssl_FreeLockedSID(sslSessionID *sid) { PORT_Assert(sid->references >= 1); if (--sid->references == 0) { ssl_DestroySID(sid, PR_TRUE); } } /* BEWARE: This function gets called for both client and server SIDs !! * Decrement reference count, and * free sid if ref count is zero, and sid is not in the cache. * Does NOT remove from the cache first. * These locks are necessary because the sid _might_ be in the cache list. */ void ssl_FreeSID(sslSessionID *sid) { if (sid) { LOCK_CACHE; ssl_FreeLockedSID(sid); UNLOCK_CACHE; } } sslSessionID * ssl_ReferenceSID(sslSessionID *sid) { LOCK_CACHE; sid->references++; UNLOCK_CACHE; return sid; } /************************************************************************/ /* ** Lookup sid entry in cache by Address, port, and peerID string. ** If found, Increment reference count, and return pointer to caller. ** If it has timed out or ref count is zero, remove from list and free it. */ sslSessionID * ssl_LookupSID(PRTime now, const PRIPv6Addr *addr, PRUint16 port, const char *peerID, const char *urlSvrName) { sslSessionID **sidp; sslSessionID *sid; if (!urlSvrName) return NULL; LOCK_CACHE; sidp = &cache; while ((sid = *sidp) != 0) { PORT_Assert(sid->cached == in_client_cache); PORT_Assert(sid->references >= 1); SSL_TRC(8, ("SSL: lookup: sid=0x%x", sid)); if (sid->expirationTime < now) { /* ** This session-id timed out. ** Don't even care who it belongs to, blow it out of our cache. */ SSL_TRC(7, ("SSL: lookup, throwing sid out, age=%d refs=%d", now - sid->creationTime, sid->references)); *sidp = sid->next; /* delink it from the list. */ sid->cached = invalid_cache; /* mark not on list. */ ssl_FreeLockedSID(sid); /* drop ref count, free. */ } else if (!memcmp(&sid->addr, addr, sizeof(PRIPv6Addr)) && /* server IP addr matches */ (sid->port == port) && /* server port matches */ /* proxy (peerID) matches */ (((peerID == NULL) && (sid->peerID == NULL)) || ((peerID != NULL) && (sid->peerID != NULL) && PORT_Strcmp(sid->peerID, peerID) == 0)) && /* is cacheable */ (sid->u.ssl3.keys.resumable) && /* server hostname matches. */ (sid->urlSvrName != NULL) && (0 == PORT_Strcmp(urlSvrName, sid->urlSvrName))) { /* Hit */ sid->lastAccessTime = now; sid->references++; break; } else { sidp = &sid->next; } } UNLOCK_CACHE; return sid; } /* ** Add an sid to the cache or return a previously cached entry to the cache. ** Although this is static, it is called via ss->sec.cache(). */ static void CacheSID(sslSessionID *sid, PRTime creationTime) { PORT_Assert(sid); PORT_Assert(sid->cached == never_cached); SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x " "time=%x cached=%d", sid, sid->cached, sid->addr.pr_s6_addr32[0], sid->addr.pr_s6_addr32[1], sid->addr.pr_s6_addr32[2], sid->addr.pr_s6_addr32[3], sid->port, sid->creationTime, sid->cached)); if (!sid->urlSvrName) { /* don't cache this SID because it can never be matched */ return; } if (sid->u.ssl3.sessionIDLength == 0 && sid->u.ssl3.locked.sessionTicket.ticket.data == NULL) return; /* Client generates the SessionID if this was a stateless resume. */ if (sid->u.ssl3.sessionIDLength == 0) { SECStatus rv; rv = PK11_GenerateRandom(sid->u.ssl3.sessionID, SSL3_SESSIONID_BYTES); if (rv != SECSuccess) return; sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES; } PRINT_BUF(8, (0, "sessionID:", sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength)); sid->u.ssl3.lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, NULL); if (!sid->u.ssl3.lock) { return; } PORT_Assert(sid->creationTime != 0); if (!sid->creationTime) { sid->lastAccessTime = sid->creationTime = creationTime; } PORT_Assert(sid->expirationTime != 0); if (!sid->expirationTime) { sid->expirationTime = sid->creationTime + (PR_MIN(ssl_ticket_lifetime, sid->u.ssl3.locked.sessionTicket.ticket_lifetime_hint) * PR_USEC_PER_SEC); } /* * Put sid into the cache. Bump reference count to indicate that * cache is holding a reference. Uncache will reduce the cache * reference. */ LOCK_CACHE; sid->references++; sid->cached = in_client_cache; sid->next = cache; cache = sid; UNLOCK_CACHE; } /* * If sid "zap" is in the cache, * removes sid from cache, and decrements reference count. * Caller must hold cache lock. */ static void UncacheSID(sslSessionID *zap) { sslSessionID **sidp = &cache; sslSessionID *sid; if (zap->cached != in_client_cache) { return; } SSL_TRC(8, ("SSL: Uncache: zap=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x " "time=%x cipherSuite=%d", zap, zap->cached, zap->addr.pr_s6_addr32[0], zap->addr.pr_s6_addr32[1], zap->addr.pr_s6_addr32[2], zap->addr.pr_s6_addr32[3], zap->port, zap->creationTime, zap->u.ssl3.cipherSuite)); /* See if it's in the cache, if so nuke it */ while ((sid = *sidp) != 0) { if (sid == zap) { /* ** Bingo. Reduce reference count by one so that when ** everyone is done with the sid we can free it up. */ *sidp = zap->next; zap->cached = invalid_cache; ssl_FreeLockedSID(zap); return; } sidp = &sid->next; } } /* If sid "zap" is in the cache, * removes sid from cache, and decrements reference count. * Although this function is static, it is called externally via * ssl_UncacheSessionID. */ static void LockAndUncacheSID(sslSessionID *zap) { LOCK_CACHE; UncacheSID(zap); UNLOCK_CACHE; } SECStatus ReadVariableFromBuffer(sslReader *reader, sslReadBuffer *readerBuffer, uint8_t lenBytes, SECItem *dest) { if (sslRead_ReadVariable(reader, lenBytes, readerBuffer) != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } if (readerBuffer->len) { SECItem tempItem = { siBuffer, (unsigned char *)readerBuffer->buf, readerBuffer->len }; SECStatus rv = SECITEM_CopyItem(NULL, dest, &tempItem); if (rv != SECSuccess) { return rv; } } return SECSuccess; } /* Fill sid with the values from the encoded resumption token. * sid has to be allocated. * We don't care about locks here as this cache entry is externally stored. */ SECStatus ssl_DecodeResumptionToken(sslSessionID *sid, const PRUint8 *encodedToken, PRUint32 encodedTokenLen) { PORT_Assert(encodedTokenLen); PORT_Assert(encodedToken); PORT_Assert(sid); if (!sid || !encodedToken || !encodedTokenLen) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } if (encodedToken[0] != SSLResumptionTokenVersion) { /* Unknown token format version. */ PORT_SetError(SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR); return SECFailure; } /* These variables are used across macros. Don't use them outside. */ sslReader reader = SSL_READER(encodedToken, encodedTokenLen); reader.offset += 1; // We read the version already. Skip the first byte. sslReadBuffer readerBuffer = { 0 }; PRUint64 tmpInt = 0; if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { return SECFailure; } sid->lastAccessTime = (PRTime)tmpInt; if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { return SECFailure; } sid->expirationTime = (PRTime)tmpInt; if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.locked.sessionTicket.received_timestamp = (PRTime)tmpInt; if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.locked.sessionTicket.ticket_lifetime_hint = (PRUint32)tmpInt; if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.locked.sessionTicket.flags = (PRUint32)tmpInt; if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.locked.sessionTicket.ticket_age_add = (PRUint32)tmpInt; if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.locked.sessionTicket.max_early_data_size = (PRUint32)tmpInt; if (sslRead_ReadVariable(&reader, 3, &readerBuffer) != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } if (readerBuffer.len) { PORT_Assert(!sid->peerCert); SECItem tempItem = { siBuffer, (unsigned char *)readerBuffer.buf, readerBuffer.len }; sid->peerCert = CERT_NewTempCertificate(NULL, /* dbHandle */ &tempItem, NULL, PR_FALSE, PR_TRUE); if (!sid->peerCert) { return SECFailure; } } if (sslRead_ReadVariable(&reader, 2, &readerBuffer) != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } if (readerBuffer.len) { SECITEM_AllocArray(NULL, &sid->peerCertStatus, 1); if (!sid->peerCertStatus.items) { return SECFailure; } SECItem tempItem = { siBuffer, (unsigned char *)readerBuffer.buf, readerBuffer.len }; SECITEM_CopyItem(NULL, &sid->peerCertStatus.items[0], &tempItem); } if (sslRead_ReadVariable(&reader, 1, &readerBuffer) != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } if (readerBuffer.len) { PORT_Assert(readerBuffer.buf); if (sid->peerID) { PORT_Free((void *)sid->peerID); } sid->peerID = PORT_Strdup((const char *)readerBuffer.buf); } if (sslRead_ReadVariable(&reader, 1, &readerBuffer) != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } if (readerBuffer.len) { if (sid->urlSvrName) { PORT_Free((void *)sid->urlSvrName); } PORT_Assert(readerBuffer.buf); sid->urlSvrName = PORT_Strdup((const char *)readerBuffer.buf); } if (sslRead_ReadVariable(&reader, 3, &readerBuffer) != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } if (readerBuffer.len) { PORT_Assert(!sid->localCert); SECItem tempItem = { siBuffer, (unsigned char *)readerBuffer.buf, readerBuffer.len }; sid->localCert = CERT_NewTempCertificate(NULL, /* dbHandle */ &tempItem, NULL, PR_FALSE, PR_TRUE); } if (sslRead_ReadNumber(&reader, 8, &sid->addr.pr_s6_addr64[0]) != SECSuccess) { return SECFailure; } if (sslRead_ReadNumber(&reader, 8, &sid->addr.pr_s6_addr64[1]) != SECSuccess) { return SECFailure; } if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { return SECFailure; } sid->port = (PRUint16)tmpInt; if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { return SECFailure; } sid->version = (PRUint16)tmpInt; if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { return SECFailure; } sid->creationTime = (PRTime)tmpInt; if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { return SECFailure; } sid->authType = (SSLAuthType)tmpInt; if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { return SECFailure; } sid->authKeyBits = (PRUint32)tmpInt; if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { return SECFailure; } sid->keaType = (SSLKEAType)tmpInt; if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { return SECFailure; } sid->keaKeyBits = (PRUint32)tmpInt; if (sslRead_ReadNumber(&reader, 3, &tmpInt) != SECSuccess) { return SECFailure; } sid->keaGroup = (SSLNamedGroup)tmpInt; if (sslRead_ReadNumber(&reader, 3, &tmpInt) != SECSuccess) { return SECFailure; } sid->sigScheme = (SSLSignatureScheme)tmpInt; if (sslRead_ReadNumber(&reader, 1, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.sessionIDLength = (PRUint8)tmpInt; if (sslRead_ReadVariable(&reader, 1, &readerBuffer) != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } if (readerBuffer.len) { PORT_Assert(readerBuffer.buf); PORT_Memcpy(sid->u.ssl3.sessionID, readerBuffer.buf, readerBuffer.len); } if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.cipherSuite = (PRUint16)tmpInt; if (sslRead_ReadNumber(&reader, 1, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.policy = (PRUint8)tmpInt; if (sslRead_ReadVariable(&reader, 1, &readerBuffer) != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } PORT_Assert(readerBuffer.len == WRAPPED_MASTER_SECRET_SIZE); if (readerBuffer.len != WRAPPED_MASTER_SECRET_SIZE) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } PORT_Assert(readerBuffer.buf); PORT_Memcpy(sid->u.ssl3.keys.wrapped_master_secret, readerBuffer.buf, readerBuffer.len); if (sslRead_ReadNumber(&reader, 1, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.keys.wrapped_master_secret_len = (PRUint8)tmpInt; if (sslRead_ReadNumber(&reader, 1, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.keys.extendedMasterSecretUsed = (PRUint8)tmpInt; if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.masterWrapMech = (unsigned long)tmpInt; if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.masterModuleID = (unsigned long)tmpInt; if (sslRead_ReadNumber(&reader, 8, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.masterSlotID = (unsigned long)tmpInt; if (sslRead_ReadNumber(&reader, 4, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.masterWrapIndex = (PRUint32)tmpInt; if (sslRead_ReadNumber(&reader, 2, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.masterWrapSeries = (PRUint16)tmpInt; if (sslRead_ReadNumber(&reader, 1, &tmpInt) != SECSuccess) { return SECFailure; } sid->u.ssl3.masterValid = (char)tmpInt; if (ReadVariableFromBuffer(&reader, &readerBuffer, 1, &sid->u.ssl3.srvName) != SECSuccess) { return SECFailure; } if (ReadVariableFromBuffer(&reader, &readerBuffer, 2, &sid->u.ssl3.signedCertTimestamps) != SECSuccess) { return SECFailure; } if (ReadVariableFromBuffer(&reader, &readerBuffer, 1, &sid->u.ssl3.alpnSelection) != SECSuccess) { return SECFailure; } if (ReadVariableFromBuffer(&reader, &readerBuffer, 2, &sid->u.ssl3.locked.sessionTicket.ticket) != SECSuccess) { return SECFailure; } if (!sid->u.ssl3.locked.sessionTicket.ticket.len) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } /* At this point we must have read everything. */ PORT_Assert(reader.offset == reader.buf.len); if (reader.offset != reader.buf.len) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } return SECSuccess; } PRBool ssl_IsResumptionTokenUsable(sslSocket *ss, sslSessionID *sid) { PORT_Assert(ss); PORT_Assert(sid); // Check that the ticket didn't expire. PRTime endTime = 0; NewSessionTicket *ticket = &sid->u.ssl3.locked.sessionTicket; if (ticket->ticket_lifetime_hint != 0) { endTime = ticket->received_timestamp + (PRTime)(ticket->ticket_lifetime_hint * PR_USEC_PER_SEC); if (endTime <= ssl_Time(ss)) { return PR_FALSE; } } // Check that the session entry didn't expire. if (sid->expirationTime < ssl_Time(ss)) { return PR_FALSE; } // Check that the server name (SNI) matches the one set for this session. // Don't use the token if there's no server name. if (sid->urlSvrName == NULL || PORT_Strcmp(ss->url, sid->urlSvrName) != 0) { return PR_FALSE; } // This shouldn't be false, but let's check it anyway. if (!sid->u.ssl3.keys.resumable) { return PR_FALSE; } return PR_TRUE; } /* Encode a session ticket into a byte array that can be handed out to a cache. * Needed memory in encodedToken has to be allocated according to * *encodedTokenLen. */ static SECStatus ssl_EncodeResumptionToken(sslSessionID *sid, sslBuffer *encodedTokenBuf) { PORT_Assert(encodedTokenBuf); PORT_Assert(sid); if (!sid || !sid->u.ssl3.locked.sessionTicket.ticket.len || !encodedTokenBuf || !sid->u.ssl3.keys.resumable || !sid->urlSvrName) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } /* Encoding format: * 0-byte: version * Integers are encoded according to their length. * SECItems are prepended with a 64-bit length field followed by the bytes. * Optional bytes are encoded as a 0-length item if not present. */ SECStatus rv = sslBuffer_AppendNumber(encodedTokenBuf, SSLResumptionTokenVersion, 1); if (rv != SECSuccess) { return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->lastAccessTime, 8); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->expirationTime, 8); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } // session ticket rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.locked.sessionTicket.received_timestamp, 8); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.locked.sessionTicket.ticket_lifetime_hint, 4); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.locked.sessionTicket.flags, 4); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.locked.sessionTicket.ticket_age_add, 4); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.locked.sessionTicket.max_early_data_size, 4); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendVariable(encodedTokenBuf, sid->peerCert->derCert.data, sid->peerCert->derCert.len, 3); if (rv != SECSuccess) { return SECFailure; } if (sid->peerCertStatus.len > 1) { /* This is not implemented so it shouldn't happen. * If it gets implemented, this has to change. */ PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } if (sid->peerCertStatus.len == 1 && sid->peerCertStatus.items[0].len) { rv = sslBuffer_AppendVariable(encodedTokenBuf, sid->peerCertStatus.items[0].data, sid->peerCertStatus.items[0].len, 2); if (rv != SECSuccess) { return SECFailure; } } else { rv = sslBuffer_AppendVariable(encodedTokenBuf, NULL, 0, 2); if (rv != SECSuccess) { return SECFailure; } } PRUint64 len = sid->peerID ? strlen(sid->peerID) : 0; if (len > PR_UINT8_MAX) { // This string really shouldn't be that long. PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } rv = sslBuffer_AppendVariable(encodedTokenBuf, (const unsigned char *)sid->peerID, len, 1); if (rv != SECSuccess) { return SECFailure; } len = sid->urlSvrName ? strlen(sid->urlSvrName) : 0; if (!len) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } if (len > PR_UINT8_MAX) { // This string really shouldn't be that long. PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } rv = sslBuffer_AppendVariable(encodedTokenBuf, (const unsigned char *)sid->urlSvrName, len, 1); if (rv != SECSuccess) { return SECFailure; } if (sid->localCert) { rv = sslBuffer_AppendVariable(encodedTokenBuf, sid->localCert->derCert.data, sid->localCert->derCert.len, 3); if (rv != SECSuccess) { return SECFailure; } } else { rv = sslBuffer_AppendVariable(encodedTokenBuf, NULL, 0, 3); if (rv != SECSuccess) { return SECFailure; } } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->addr.pr_s6_addr64[0], 8); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->addr.pr_s6_addr64[1], 8); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->port, 2); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->version, 2); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->creationTime, 8); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->authType, 2); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->authKeyBits, 4); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->keaType, 2); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->keaKeyBits, 4); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->keaGroup, 3); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->sigScheme, 3); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.sessionIDLength, 1); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendVariable(encodedTokenBuf, sid->u.ssl3.sessionID, SSL3_SESSIONID_BYTES, 1); if (rv != SECSuccess) { return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.cipherSuite, 2); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.policy, 1); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendVariable(encodedTokenBuf, sid->u.ssl3.keys.wrapped_master_secret, WRAPPED_MASTER_SECRET_SIZE, 1); if (rv != SECSuccess) { return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.keys.wrapped_master_secret_len, 1); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.keys.extendedMasterSecretUsed, 1); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.masterWrapMech, 8); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.masterModuleID, 8); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.masterSlotID, 8); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.masterWrapIndex, 4); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.masterWrapSeries, 2); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendNumber(encodedTokenBuf, sid->u.ssl3.masterValid, 1); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } rv = sslBuffer_AppendVariable(encodedTokenBuf, sid->u.ssl3.srvName.data, sid->u.ssl3.srvName.len, 1); if (rv != SECSuccess) { return SECFailure; } rv = sslBuffer_AppendVariable(encodedTokenBuf, sid->u.ssl3.signedCertTimestamps.data, sid->u.ssl3.signedCertTimestamps.len, 2); if (rv != SECSuccess) { return SECFailure; } rv = sslBuffer_AppendVariable(encodedTokenBuf, sid->u.ssl3.alpnSelection.data, sid->u.ssl3.alpnSelection.len, 1); if (rv != SECSuccess) { return SECFailure; } PORT_Assert(sid->u.ssl3.locked.sessionTicket.ticket.len > 1); rv = sslBuffer_AppendVariable(encodedTokenBuf, sid->u.ssl3.locked.sessionTicket.ticket.data, sid->u.ssl3.locked.sessionTicket.ticket.len, 2); if (rv != SECSuccess) { return SECFailure; } return SECSuccess; } void ssl_CacheExternalToken(sslSocket *ss) { PORT_Assert(ss); sslSessionID *sid = ss->sec.ci.sid; PORT_Assert(sid); PORT_Assert(sid->cached == never_cached); PORT_Assert(ss->resumptionTokenCallback); SSL_TRC(8, ("SSL [%d]: Cache External: sid=0x%x cached=%d " "addr=0x%08x%08x%08x%08x port=0x%04x time=%x cached=%d", ss->fd, sid, sid->cached, sid->addr.pr_s6_addr32[0], sid->addr.pr_s6_addr32[1], sid->addr.pr_s6_addr32[2], sid->addr.pr_s6_addr32[3], sid->port, sid->creationTime, sid->cached)); /* This is only available for stateless resumption. */ if (sid->u.ssl3.locked.sessionTicket.ticket.data == NULL) { return; } /* Don't export token if the session used client authentication. */ if (sid->u.ssl3.clAuthValid) { return; } if (!sid->creationTime) { sid->lastAccessTime = sid->creationTime = ssl_Time(ss); } if (!sid->expirationTime) { sid->expirationTime = sid->creationTime + (PR_MIN(ssl_ticket_lifetime, sid->u.ssl3.locked.sessionTicket.ticket_lifetime_hint) * PR_USEC_PER_SEC); } sslBuffer encodedToken = SSL_BUFFER_EMPTY; if (ssl_EncodeResumptionToken(sid, &encodedToken) != SECSuccess) { SSL_TRC(3, ("SSL [%d]: encoding resumption token failed", ss->fd)); return; } PORT_Assert(SSL_BUFFER_LEN(&encodedToken) > 0); PRINT_BUF(40, (ss, "SSL: encoded resumption token", SSL_BUFFER_BASE(&encodedToken), SSL_BUFFER_LEN(&encodedToken))); SECStatus rv = ss->resumptionTokenCallback( ss->fd, SSL_BUFFER_BASE(&encodedToken), SSL_BUFFER_LEN(&encodedToken), ss->resumptionTokenContext); if (rv == SECSuccess) { sid->cached = in_external_cache; } sslBuffer_Clear(&encodedToken); } void ssl_CacheSessionID(sslSocket *ss) { sslSecurityInfo *sec = &ss->sec; PORT_Assert(sec); PORT_Assert(sec->ci.sid->cached == never_cached); if (sec->ci.sid && !sec->ci.sid->u.ssl3.keys.resumable) { return; } if (!sec->isServer && ss->resumptionTokenCallback) { ssl_CacheExternalToken(ss); return; } PORT_Assert(!ss->resumptionTokenCallback); if (sec->isServer) { ssl_ServerCacheSessionID(sec->ci.sid, ssl_Time(ss)); return; } CacheSID(sec->ci.sid, ssl_Time(ss)); } void ssl_UncacheSessionID(sslSocket *ss) { if (ss->opt.noCache) { return; } sslSecurityInfo *sec = &ss->sec; PORT_Assert(sec); if (sec->ci.sid) { if (sec->isServer) { ssl_ServerUncacheSessionID(sec->ci.sid); } else if (!ss->resumptionTokenCallback) { LockAndUncacheSID(sec->ci.sid); } } } /* wipe out the entire client session cache. */ void SSL_ClearSessionCache(void) { LOCK_CACHE; while (cache != NULL) UncacheSID(cache); UNLOCK_CACHE; } PRBool ssl_TicketTimeValid(const sslSocket *ss, const NewSessionTicket *ticket) { PRTime endTime; if (ticket->ticket_lifetime_hint == 0) { return PR_TRUE; } endTime = ticket->received_timestamp + (PRTime)(ticket->ticket_lifetime_hint * PR_USEC_PER_SEC); return endTime > ssl_Time(ss); } void ssl3_SetSIDSessionTicket(sslSessionID *sid, /*in/out*/ NewSessionTicket *newSessionTicket) { PORT_Assert(sid); PORT_Assert(newSessionTicket); PORT_Assert(newSessionTicket->ticket.data); PORT_Assert(newSessionTicket->ticket.len != 0); /* If this is in the client cache, we are updating an existing entry that is * already cached or was once cached, so we need to acquire and release the * write lock. Otherwise, this is a new session that isn't shared with * anything yet, so no locking is needed. */ if (sid->u.ssl3.lock) { PR_RWLock_Wlock(sid->u.ssl3.lock); /* Another thread may have evicted, or it may be in external cache. */ PORT_Assert(sid->cached != never_cached); } /* If this was in the client cache, then we might have to free the old * ticket. In TLS 1.3, we might get a replacement ticket if the server * sends more than one ticket. */ if (sid->u.ssl3.locked.sessionTicket.ticket.data) { PORT_Assert(sid->cached != never_cached || sid->version >= SSL_LIBRARY_VERSION_TLS_1_3); SECITEM_FreeItem(&sid->u.ssl3.locked.sessionTicket.ticket, PR_FALSE); } PORT_Assert(!sid->u.ssl3.locked.sessionTicket.ticket.data); /* Do a shallow copy, moving the ticket data. */ sid->u.ssl3.locked.sessionTicket = *newSessionTicket; newSessionTicket->ticket.data = NULL; newSessionTicket->ticket.len = 0; if (sid->u.ssl3.lock) { PR_RWLock_Unlock(sid->u.ssl3.lock); } }