diff options
Diffstat (limited to 'security/nss/lib/ssl/sslsnce.c')
-rw-r--r-- | security/nss/lib/ssl/sslsnce.c | 1990 |
1 files changed, 1990 insertions, 0 deletions
diff --git a/security/nss/lib/ssl/sslsnce.c b/security/nss/lib/ssl/sslsnce.c new file mode 100644 index 000000000..4a4005c2d --- /dev/null +++ b/security/nss/lib/ssl/sslsnce.c @@ -0,0 +1,1990 @@ +/* This file implements the SERVER Session ID cache. + * NOTE: The contents of this file are NOT used by the client. + * + * 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/. */ + +/* Note: ssl_FreeSID() in sslnonce.c gets used for both client and server + * cache sids! + * + * About record locking among different server processes: + * + * All processes that are part of the same conceptual server (serving on + * the same address and port) MUST share a common SSL session cache. + * This code makes the content of the shared cache accessible to all + * processes on the same "server". This code works on Unix and Win32 only. + * + * We use NSPR anonymous shared memory and move data to & from shared memory. + * We must do explicit locking of the records for all reads and writes. + * The set of Cache entries are divided up into "sets" of 128 entries. + * Each set is protected by a lock. There may be one or more sets protected + * by each lock. That is, locks to sets are 1:N. + * There is one lock for the entire cert cache. + * There is one lock for the set of wrapped sym wrap keys. + * + * The anonymous shared memory is laid out as if it were declared like this: + * + * struct { + * cacheDescriptor desc; + * sidCacheLock sidCacheLocks[ numSIDCacheLocks]; + * sidCacheLock keyCacheLock; + * sidCacheLock certCacheLock; + * sidCacheSet sidCacheSets[ numSIDCacheSets ]; + * sidCacheEntry sidCacheData[ numSIDCacheEntries]; + * certCacheEntry certCacheData[numCertCacheEntries]; + * SSLWrappedSymWrappingKey keyCacheData[ssl_auth_size][SSL_NUM_WRAP_MECHS]; + * PRUint8 keyNameSuffix[SESS_TICKET_KEY_VAR_NAME_LEN] + * encKeyCacheEntry ticketEncKey; // Wrapped + * encKeyCacheEntry ticketMacKey; // Wrapped + * PRBool ticketKeysValid; + * sidCacheLock srvNameCacheLock; + * srvNameCacheEntry srvNameData[ numSrvNameCacheEntries ]; + * } cacheMemCacheData; + */ +#include "seccomon.h" + +#if defined(XP_UNIX) || defined(XP_WIN32) || defined(XP_OS2) || defined(XP_BEOS) + +#include "cert.h" +#include "ssl.h" +#include "sslimpl.h" +#include "sslproto.h" +#include "pk11func.h" +#include "base64.h" +#include "keyhi.h" +#include "blapit.h" +#include "sechash.h" + +#include <stdio.h> + +#if defined(XP_UNIX) || defined(XP_BEOS) + +#include <syslog.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include "unix_err.h" + +#else + +#ifdef XP_WIN32 +#include <wtypes.h> +#include "win32err.h" +#endif + +#endif +#include <sys/types.h> + +#include "nspr.h" +#include "sslmutex.h" + +/* +** Format of a cache entry in the shared memory. +*/ +struct sidCacheEntryStr { + /* 16 */ PRIPv6Addr addr; /* client's IP address */ + /* 4 */ PRUint32 creationTime; + /* 4 */ PRUint32 lastAccessTime; + /* 4 */ PRUint32 expirationTime; + /* 2 */ PRUint16 version; + /* 1 */ PRUint8 valid; + /* 1 */ PRUint8 sessionIDLength; + /* 32 */ PRUint8 sessionID[SSL3_SESSIONID_BYTES]; + /* 2 */ PRUint16 authType; + /* 2 */ PRUint16 authKeyBits; + /* 2 */ PRUint16 keaType; + /* 2 */ PRUint16 keaKeyBits; + /* 72 - common header total */ + + union { + struct { + /* 2 */ ssl3CipherSuite cipherSuite; + /* 2 */ PRUint16 compression; /* SSLCompressionMethod */ + + /* 54 */ ssl3SidKeys keys; /* keys, wrapped as needed. */ + + /* 4 */ PRUint32 masterWrapMech; + /* 4 */ PRInt32 certIndex; + /* 4 */ PRInt32 srvNameIndex; + /* 32 */ PRUint8 srvNameHash[SHA256_LENGTH]; /* SHA256 name hash */ + /* 2 */ PRUint16 certTypeArgs; +/*104 */} ssl3; + +/* force sizeof(sidCacheEntry) to be a multiple of cache line size */ +struct { + /*120 */ PRUint8 filler[120]; /* 72+120==192, a multiple of 16 */ +} forceSize; + } u; +}; +typedef struct sidCacheEntryStr sidCacheEntry; + +/* The length of this struct is supposed to be a power of 2, e.g. 4KB */ +struct certCacheEntryStr { + PRUint16 certLength; /* 2 */ + PRUint16 sessionIDLength; /* 2 */ + PRUint8 sessionID[SSL3_SESSIONID_BYTES]; /* 32 */ + PRUint8 cert[SSL_MAX_CACHED_CERT_LEN]; /* 4060 */ +}; /* total 4096 */ +typedef struct certCacheEntryStr certCacheEntry; + +struct sidCacheLockStr { + PRUint32 timeStamp; + sslMutex mutex; + sslPID pid; +}; +typedef struct sidCacheLockStr sidCacheLock; + +struct sidCacheSetStr { + PRIntn next; +}; +typedef struct sidCacheSetStr sidCacheSet; + +struct encKeyCacheEntryStr { + PRUint8 bytes[512]; + PRInt32 length; +}; +typedef struct encKeyCacheEntryStr encKeyCacheEntry; + +#define SSL_MAX_DNS_HOST_NAME 1024 + +struct srvNameCacheEntryStr { + PRUint16 type; /* 2 */ + PRUint16 nameLen; /* 2 */ + PRUint8 name[SSL_MAX_DNS_HOST_NAME + 12]; /* 1034 */ + PRUint8 nameHash[SHA256_LENGTH]; /* 32 */ + /* 1072 */ +}; +typedef struct srvNameCacheEntryStr srvNameCacheEntry; + +struct cacheDescStr { + + PRUint32 cacheMemSize; + + PRUint32 numSIDCacheLocks; + PRUint32 numSIDCacheSets; + PRUint32 numSIDCacheSetsPerLock; + + PRUint32 numSIDCacheEntries; + PRUint32 sidCacheSize; + + PRUint32 numCertCacheEntries; + PRUint32 certCacheSize; + + PRUint32 numKeyCacheEntries; + PRUint32 keyCacheSize; + + PRUint32 numSrvNameCacheEntries; + PRUint32 srvNameCacheSize; + + PRUint32 ssl3Timeout; + + PRUint32 numSIDCacheLocksInitialized; + + /* These values are volatile, and are accessed through sharedCache-> */ + PRUint32 nextCertCacheEntry; /* certCacheLock protects */ + PRBool stopPolling; + PRBool everInherited; + + /* The private copies of these values are pointers into shared mem */ + /* The copies of these values in shared memory are merely offsets */ + sidCacheLock *sidCacheLocks; + sidCacheLock *keyCacheLock; + sidCacheLock *certCacheLock; + sidCacheLock *srvNameCacheLock; + sidCacheSet *sidCacheSets; + sidCacheEntry *sidCacheData; + certCacheEntry *certCacheData; + SSLWrappedSymWrappingKey *keyCacheData; + PRUint8 *ticketKeyNameSuffix; + encKeyCacheEntry *ticketEncKey; + encKeyCacheEntry *ticketMacKey; + PRUint32 *ticketKeysValid; + srvNameCacheEntry *srvNameCacheData; + + /* Only the private copies of these pointers are valid */ + char *cacheMem; + struct cacheDescStr *sharedCache; /* shared copy of this struct */ + PRFileMap *cacheMemMap; + PRThread *poller; + PRUint32 mutexTimeout; + PRBool shared; +}; +typedef struct cacheDescStr cacheDesc; + +static cacheDesc globalCache; + +static const char envVarName[] = { SSL_ENV_VAR_NAME }; + +static PRBool isMultiProcess = PR_FALSE; + +#define DEF_SID_CACHE_ENTRIES 10000 +#define DEF_CERT_CACHE_ENTRIES 250 +#define MIN_CERT_CACHE_ENTRIES 125 /* the effective size in old releases. */ +#define DEF_KEY_CACHE_ENTRIES 250 +#define DEF_NAME_CACHE_ENTRIES 1000 + +#define SID_CACHE_ENTRIES_PER_SET 128 +#define SID_ALIGNMENT 16 + +#define DEF_SSL3_TIMEOUT 86400L /* 24 hours */ +#define MAX_SSL3_TIMEOUT 86400L /* 24 hours */ +#define MIN_SSL3_TIMEOUT 5 /* seconds */ + +#if defined(AIX) || defined(LINUX) || defined(NETBSD) || defined(OPENBSD) +#define MAX_SID_CACHE_LOCKS 8 /* two FDs per lock */ +#elif defined(OSF1) +#define MAX_SID_CACHE_LOCKS 16 /* one FD per lock */ +#else +#define MAX_SID_CACHE_LOCKS 256 +#endif + +#define SID_HOWMANY(val, size) (((val) + ((size)-1)) / (size)) +#define SID_ROUNDUP(val, size) ((size)*SID_HOWMANY((val), (size))) + +static sslPID myPid; +static PRUint32 ssl_max_sid_cache_locks = MAX_SID_CACHE_LOCKS; + +/* forward static function declarations */ +static PRUint32 SIDindex(cacheDesc *cache, const PRIPv6Addr *addr, PRUint8 *s, + unsigned nl); +static SECStatus LaunchLockPoller(cacheDesc *cache); +static SECStatus StopLockPoller(cacheDesc *cache); + +struct inheritanceStr { + PRUint32 cacheMemSize; + PRUint32 fmStrLen; +}; + +typedef struct inheritanceStr inheritance; + +#if defined(_WIN32) || defined(XP_OS2) + +#define DEFAULT_CACHE_DIRECTORY "\\temp" + +#endif /* _win32 */ + +#if defined(XP_UNIX) || defined(XP_BEOS) + +#define DEFAULT_CACHE_DIRECTORY "/tmp" + +#endif /* XP_UNIX || XP_BEOS */ + +/************************************************************************/ + +static PRUint32 +LockSidCacheLock(sidCacheLock *lock, PRUint32 now) +{ + SECStatus rv = sslMutex_Lock(&lock->mutex); + if (rv != SECSuccess) + return 0; + if (!now) + now = ssl_Time(); + lock->timeStamp = now; + lock->pid = myPid; + return now; +} + +static SECStatus +UnlockSidCacheLock(sidCacheLock *lock) +{ + SECStatus rv; + + lock->pid = 0; + rv = sslMutex_Unlock(&lock->mutex); + return rv; +} + +/* returns the value of ssl_Time on success, zero on failure. */ +static PRUint32 +LockSet(cacheDesc *cache, PRUint32 set, PRUint32 now) +{ + PRUint32 lockNum = set % cache->numSIDCacheLocks; + sidCacheLock *lock = cache->sidCacheLocks + lockNum; + + return LockSidCacheLock(lock, now); +} + +static SECStatus +UnlockSet(cacheDesc *cache, PRUint32 set) +{ + PRUint32 lockNum = set % cache->numSIDCacheLocks; + sidCacheLock *lock = cache->sidCacheLocks + lockNum; + + return UnlockSidCacheLock(lock); +} + +/************************************************************************/ + +/* Put a certificate in the cache. Update the cert index in the sce. +*/ +static PRUint32 +CacheCert(cacheDesc *cache, CERTCertificate *cert, sidCacheEntry *sce) +{ + PRUint32 now; + certCacheEntry cce; + + if ((cert->derCert.len > SSL_MAX_CACHED_CERT_LEN) || + (cert->derCert.len <= 0) || + (cert->derCert.data == NULL)) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return 0; + } + + cce.sessionIDLength = sce->sessionIDLength; + PORT_Memcpy(cce.sessionID, sce->sessionID, cce.sessionIDLength); + + cce.certLength = cert->derCert.len; + PORT_Memcpy(cce.cert, cert->derCert.data, cce.certLength); + + /* get lock on cert cache */ + now = LockSidCacheLock(cache->certCacheLock, 0); + if (now) { + + /* Find where to place the next cert cache entry. */ + cacheDesc *sharedCache = cache->sharedCache; + PRUint32 ndx = sharedCache->nextCertCacheEntry; + + /* write the entry */ + cache->certCacheData[ndx] = cce; + + /* remember where we put it. */ + sce->u.ssl3.certIndex = ndx; + + /* update the "next" cache entry index */ + sharedCache->nextCertCacheEntry = + (ndx + 1) % cache->numCertCacheEntries; + + UnlockSidCacheLock(cache->certCacheLock); + } + return now; +} + +/* Server configuration hash tables need to account the SECITEM.type + * field as well. These functions accomplish that. */ +static PLHashNumber +Get32BitNameHash(const SECItem *name) +{ + PLHashNumber rv = SECITEM_Hash(name); + + PRUint8 *rvc = (PRUint8 *)&rv; + rvc[name->len % sizeof(rv)] ^= name->type; + + return rv; +} + +/* Put a name in the cache. Update the cert index in the sce. +*/ +static PRUint32 +CacheSrvName(cacheDesc *cache, SECItem *name, sidCacheEntry *sce) +{ + PRUint32 now; + PRUint32 ndx; + srvNameCacheEntry snce; + + if (!name || name->len <= 0 || + name->len > SSL_MAX_DNS_HOST_NAME) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return 0; + } + + snce.type = name->type; + snce.nameLen = name->len; + PORT_Memcpy(snce.name, name->data, snce.nameLen); + HASH_HashBuf(HASH_AlgSHA256, snce.nameHash, name->data, name->len); + + /* get index of the next name */ + ndx = Get32BitNameHash(name); + /* get lock on cert cache */ + now = LockSidCacheLock(cache->srvNameCacheLock, 0); + if (now) { + if (cache->numSrvNameCacheEntries > 0) { + /* Fit the index into array */ + ndx %= cache->numSrvNameCacheEntries; + /* write the entry */ + cache->srvNameCacheData[ndx] = snce; + /* remember where we put it. */ + sce->u.ssl3.srvNameIndex = ndx; + /* Copy hash into sid hash */ + PORT_Memcpy(sce->u.ssl3.srvNameHash, snce.nameHash, SHA256_LENGTH); + } + UnlockSidCacheLock(cache->srvNameCacheLock); + } + return now; +} + +/* +** Convert local SID to shared memory one +*/ +static void +ConvertFromSID(sidCacheEntry *to, sslSessionID *from) +{ + to->valid = 1; + to->version = from->version; + to->addr = from->addr; + to->creationTime = from->creationTime; + to->lastAccessTime = from->lastAccessTime; + to->expirationTime = from->expirationTime; + to->authType = from->authType; + to->authKeyBits = from->authKeyBits; + to->keaType = from->keaType; + to->keaKeyBits = from->keaKeyBits; + + to->u.ssl3.cipherSuite = from->u.ssl3.cipherSuite; + to->u.ssl3.compression = (PRUint16)from->u.ssl3.compression; + to->u.ssl3.keys = from->u.ssl3.keys; + to->u.ssl3.masterWrapMech = from->u.ssl3.masterWrapMech; + to->sessionIDLength = from->u.ssl3.sessionIDLength; + to->u.ssl3.certIndex = -1; + to->u.ssl3.srvNameIndex = -1; + PORT_Memcpy(to->sessionID, from->u.ssl3.sessionID, + to->sessionIDLength); + to->u.ssl3.certTypeArgs = 0U; + switch (from->authType) { + case ssl_auth_ecdsa: + case ssl_auth_ecdh_rsa: + case ssl_auth_ecdh_ecdsa: + PORT_Assert(from->certType.namedCurve); + to->u.ssl3.certTypeArgs = + (PRUint16)from->certType.namedCurve->name; + break; + default: + break; + } + + SSL_TRC(8, ("%d: SSL3: ConvertSID: time=%d addr=0x%08x%08x%08x%08x " + "cipherSuite=%d", + myPid, to->creationTime, to->addr.pr_s6_addr32[0], + to->addr.pr_s6_addr32[1], to->addr.pr_s6_addr32[2], + to->addr.pr_s6_addr32[3], to->u.ssl3.cipherSuite)); +} + +/* +** Convert shared memory cache-entry to local memory based one +** This is only called from ServerSessionIDLookup(). +*/ +static sslSessionID * +ConvertToSID(sidCacheEntry *from, + certCacheEntry *pcce, + srvNameCacheEntry *psnce, + CERTCertDBHandle *dbHandle) +{ + sslSessionID *to; + + to = PORT_ZNew(sslSessionID); + if (!to) { + return 0; + } + + to->u.ssl3.sessionIDLength = from->sessionIDLength; + to->u.ssl3.cipherSuite = from->u.ssl3.cipherSuite; + to->u.ssl3.compression = (SSLCompressionMethod)from->u.ssl3.compression; + to->u.ssl3.keys = from->u.ssl3.keys; + to->u.ssl3.masterWrapMech = from->u.ssl3.masterWrapMech; + if (from->u.ssl3.srvNameIndex != -1 && psnce) { + SECItem name; + SECStatus rv; + name.type = psnce->type; + name.len = psnce->nameLen; + name.data = psnce->name; + rv = SECITEM_CopyItem(NULL, &to->u.ssl3.srvName, &name); + if (rv != SECSuccess) { + goto loser; + } + } + + PORT_Memcpy(to->u.ssl3.sessionID, from->sessionID, from->sessionIDLength); + + /* the portions of the SID that are only restored on the client + * are set to invalid values on the server. + */ + to->u.ssl3.clientWriteKey = NULL; + to->u.ssl3.serverWriteKey = NULL; + + to->urlSvrName = NULL; + + to->u.ssl3.masterModuleID = (SECMODModuleID)-1; /* invalid value */ + to->u.ssl3.masterSlotID = (CK_SLOT_ID)-1; /* invalid value */ + to->u.ssl3.masterWrapIndex = 0; + to->u.ssl3.masterWrapSeries = 0; + to->u.ssl3.masterValid = PR_FALSE; + + to->u.ssl3.clAuthModuleID = (SECMODModuleID)-1; /* invalid value */ + to->u.ssl3.clAuthSlotID = (CK_SLOT_ID)-1; /* invalid value */ + to->u.ssl3.clAuthSeries = 0; + to->u.ssl3.clAuthValid = PR_FALSE; + + if (from->u.ssl3.certIndex != -1 && pcce) { + SECItem derCert; + + derCert.len = pcce->certLength; + derCert.data = pcce->cert; + + to->peerCert = CERT_NewTempCertificate(dbHandle, &derCert, NULL, + PR_FALSE, PR_TRUE); + if (to->peerCert == NULL) + goto loser; + } + to->certType.authType = from->authType; + switch (from->authType) { + case ssl_auth_ecdsa: + case ssl_auth_ecdh_rsa: + case ssl_auth_ecdh_ecdsa: + to->certType.namedCurve = + ssl_LookupNamedGroup((SSLNamedGroup)from->u.ssl3.certTypeArgs); + break; + default: + break; + } + + to->version = from->version; + to->creationTime = from->creationTime; + to->lastAccessTime = from->lastAccessTime; + to->expirationTime = from->expirationTime; + to->cached = in_server_cache; + to->addr = from->addr; + to->references = 1; + to->authType = from->authType; + to->authKeyBits = from->authKeyBits; + to->keaType = from->keaType; + to->keaKeyBits = from->keaKeyBits; + + return to; + +loser: + if (to) { + SECITEM_FreeItem(&to->u.ssl3.srvName, PR_FALSE); + PORT_Free(to); + } + return NULL; +} + +/* +** Perform some mumbo jumbo on the ip-address and the session-id value to +** compute a hash value. +*/ +static PRUint32 +SIDindex(cacheDesc *cache, const PRIPv6Addr *addr, PRUint8 *s, unsigned nl) +{ + PRUint32 rv; + PRUint32 x[8]; + + memset(x, 0, sizeof x); + if (nl > sizeof x) + nl = sizeof x; + memcpy(x, s, nl); + + rv = (addr->pr_s6_addr32[0] ^ addr->pr_s6_addr32[1] ^ + addr->pr_s6_addr32[2] ^ addr->pr_s6_addr32[3] ^ + x[0] ^ x[1] ^ x[2] ^ x[3] ^ x[4] ^ x[5] ^ x[6] ^ x[7]) % + cache->numSIDCacheSets; + return rv; +} + +/* +** Look something up in the cache. This will invalidate old entries +** in the process. Caller has locked the cache set! +** Returns PR_TRUE if found a valid match. PR_FALSE otherwise. +*/ +static sidCacheEntry * +FindSID(cacheDesc *cache, PRUint32 setNum, PRUint32 now, + const PRIPv6Addr *addr, unsigned char *sessionID, + unsigned sessionIDLength) +{ + PRUint32 ndx = cache->sidCacheSets[setNum].next; + int i; + + sidCacheEntry *set = cache->sidCacheData + + (setNum * SID_CACHE_ENTRIES_PER_SET); + + for (i = SID_CACHE_ENTRIES_PER_SET; i > 0; --i) { + sidCacheEntry *sce; + + ndx = (ndx - 1) % SID_CACHE_ENTRIES_PER_SET; + sce = set + ndx; + + if (!sce->valid) + continue; + + if (now > sce->expirationTime) { + /* SessionID has timed out. Invalidate the entry. */ + SSL_TRC(7, ("%d: timed out sid entry addr=%08x%08x%08x%08x now=%x " + "time+=%x", + myPid, sce->addr.pr_s6_addr32[0], + sce->addr.pr_s6_addr32[1], sce->addr.pr_s6_addr32[2], + sce->addr.pr_s6_addr32[3], now, + sce->expirationTime)); + sce->valid = 0; + continue; + } + + /* + ** Next, examine specific session-id/addr data to see if the cache + ** entry matches our addr+session-id value + */ + if (sessionIDLength == sce->sessionIDLength && + !memcmp(&sce->addr, addr, sizeof(PRIPv6Addr)) && + !memcmp(sce->sessionID, sessionID, sessionIDLength)) { + /* Found it */ + return sce; + } + } + + PORT_SetError(SSL_ERROR_SESSION_NOT_FOUND); + return NULL; +} + +/************************************************************************/ + +/* This is the primary function for finding entries in the server's sid cache. + * Although it is static, this function is called via the global function + * pointer ssl_sid_lookup. + */ +static sslSessionID * +ServerSessionIDLookup(const PRIPv6Addr *addr, + unsigned char *sessionID, + unsigned int sessionIDLength, + CERTCertDBHandle *dbHandle) +{ + sslSessionID *sid = 0; + sidCacheEntry *psce; + certCacheEntry *pcce = 0; + srvNameCacheEntry *psnce = 0; + cacheDesc *cache = &globalCache; + PRUint32 now; + PRUint32 set; + PRInt32 cndx; + sidCacheEntry sce; + certCacheEntry cce; + srvNameCacheEntry snce; + + set = SIDindex(cache, addr, sessionID, sessionIDLength); + now = LockSet(cache, set, 0); + if (!now) + return NULL; + + psce = FindSID(cache, set, now, addr, sessionID, sessionIDLength); + if (psce) { + if ((cndx = psce->u.ssl3.certIndex) != -1) { + PRUint32 gotLock = LockSidCacheLock(cache->certCacheLock, now); + if (gotLock) { + pcce = &cache->certCacheData[cndx]; + + /* See if the cert's session ID matches the sce cache. */ + if ((pcce->sessionIDLength == psce->sessionIDLength) && + !PORT_Memcmp(pcce->sessionID, psce->sessionID, + pcce->sessionIDLength)) { + cce = *pcce; + } else { + /* The cert doesen't match the SID cache entry, + ** so invalidate the SID cache entry. + */ + psce->valid = 0; + psce = 0; + pcce = 0; + } + UnlockSidCacheLock(cache->certCacheLock); + } else { + /* what the ??. Didn't get the cert cache lock. + ** Don't invalidate the SID cache entry, but don't find it. + */ + PORT_Assert(!("Didn't get cert Cache Lock!")); + psce = 0; + pcce = 0; + } + } + if (psce && ((cndx = psce->u.ssl3.srvNameIndex) != -1)) { + PRUint32 gotLock = LockSidCacheLock(cache->srvNameCacheLock, + now); + if (gotLock) { + psnce = &cache->srvNameCacheData[cndx]; + + if (!PORT_Memcmp(psnce->nameHash, psce->u.ssl3.srvNameHash, + SHA256_LENGTH)) { + snce = *psnce; + } else { + /* The name doesen't match the SID cache entry, + ** so invalidate the SID cache entry. + */ + psce->valid = 0; + psce = 0; + psnce = 0; + } + UnlockSidCacheLock(cache->srvNameCacheLock); + } else { + /* what the ??. Didn't get the cert cache lock. + ** Don't invalidate the SID cache entry, but don't find it. + */ + PORT_Assert(!("Didn't get name Cache Lock!")); + psce = 0; + psnce = 0; + } + } + if (psce) { + psce->lastAccessTime = now; + sce = *psce; /* grab a copy while holding the lock */ + } + } + UnlockSet(cache, set); + if (psce) { + /* sce conains a copy of the cache entry. + ** Convert shared memory format to local format + */ + sid = ConvertToSID(&sce, pcce ? &cce : 0, psnce ? &snce : 0, dbHandle); + } + return sid; +} + +/* +** Place a sid into the cache, if it isn't already there. +*/ +static void +ServerSessionIDCache(sslSessionID *sid) +{ + sidCacheEntry sce; + PRUint32 now = 0; + cacheDesc *cache = &globalCache; + + if (sid->u.ssl3.sessionIDLength == 0) { + return; + } + + if (sid->cached == never_cached || sid->cached == invalid_cache) { + PRUint32 set; + SECItem *name; + + PORT_Assert(sid->creationTime != 0); + if (!sid->creationTime) + sid->lastAccessTime = sid->creationTime = ssl_Time(); + /* override caller's expiration time, which uses client timeout + * duration, not server timeout duration. + */ + sid->expirationTime = sid->creationTime + cache->ssl3Timeout; + SSL_TRC(8, ("%d: SSL: CacheMT: cached=%d addr=0x%08x%08x%08x%08x time=%x " + "cipherSuite=%d", + myPid, 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->creationTime, sid->u.ssl3.cipherSuite)); + PRINT_BUF(8, (0, "sessionID:", sid->u.ssl3.sessionID, + sid->u.ssl3.sessionIDLength)); + + ConvertFromSID(&sce, sid); + + name = &sid->u.ssl3.srvName; + if (name->len && name->data) { + now = CacheSrvName(cache, name, &sce); + } + if (sid->peerCert != NULL) { + now = CacheCert(cache, sid->peerCert, &sce); + } + + set = SIDindex(cache, &sce.addr, sce.sessionID, sce.sessionIDLength); + now = LockSet(cache, set, now); + if (now) { + PRUint32 next = cache->sidCacheSets[set].next; + PRUint32 ndx = set * SID_CACHE_ENTRIES_PER_SET + next; + + /* Write out new cache entry */ + cache->sidCacheData[ndx] = sce; + + cache->sidCacheSets[set].next = + (next + 1) % SID_CACHE_ENTRIES_PER_SET; + + UnlockSet(cache, set); + sid->cached = in_server_cache; + } + } +} + +/* +** Although this is static, it is called from ssl via global function pointer +** ssl_sid_uncache. This invalidates the referenced cache entry. +*/ +static void +ServerSessionIDUncache(sslSessionID *sid) +{ + cacheDesc *cache = &globalCache; + PRUint8 *sessionID; + unsigned int sessionIDLength; + PRErrorCode err; + PRUint32 set; + PRUint32 now; + sidCacheEntry *psce; + + if (sid == NULL) + return; + + /* Uncaching a SID should never change the error code. + ** So save it here and restore it before exiting. + */ + err = PR_GetError(); + + sessionID = sid->u.ssl3.sessionID; + sessionIDLength = sid->u.ssl3.sessionIDLength; + SSL_TRC(8, ("%d: SSL3: UncacheMT: valid=%d addr=0x%08x%08x%08x%08x time=%x " + "cipherSuite=%d", + myPid, 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->creationTime, sid->u.ssl3.cipherSuite)); + PRINT_BUF(8, (0, "sessionID:", sessionID, sessionIDLength)); + set = SIDindex(cache, &sid->addr, sessionID, sessionIDLength); + now = LockSet(cache, set, 0); + if (now) { + psce = FindSID(cache, set, now, &sid->addr, sessionID, sessionIDLength); + if (psce) { + psce->valid = 0; + } + UnlockSet(cache, set); + } + sid->cached = invalid_cache; + PORT_SetError(err); +} + +#ifdef XP_OS2 + +#define INCL_DOSPROCESS +#include <os2.h> + +long +gettid(void) +{ + PTIB ptib; + PPIB ppib; + DosGetInfoBlocks(&ptib, &ppib); + return ((long)ptib->tib_ordinal); /* thread id */ +} +#endif + +static void +CloseCache(cacheDesc *cache) +{ + int locks_initialized = cache->numSIDCacheLocksInitialized; + + if (cache->cacheMem) { + if (cache->sharedCache) { + sidCacheLock *pLock = cache->sidCacheLocks; + for (; locks_initialized > 0; --locks_initialized, ++pLock) { + /* If everInherited is true, this shared cache was (and may + ** still be) in use by multiple processes. We do not wish to + ** destroy the mutexes while they are still in use, but we do + ** want to free mutex resources associated with this process. + */ + sslMutex_Destroy(&pLock->mutex, + cache->sharedCache->everInherited); + } + } + if (cache->shared) { + PR_MemUnmap(cache->cacheMem, cache->cacheMemSize); + } else { + PORT_Free(cache->cacheMem); + } + cache->cacheMem = NULL; + } + if (cache->cacheMemMap) { + PR_CloseFileMap(cache->cacheMemMap); + cache->cacheMemMap = NULL; + } + memset(cache, 0, sizeof *cache); +} + +static SECStatus +InitCache(cacheDesc *cache, int maxCacheEntries, int maxCertCacheEntries, + int maxSrvNameCacheEntries, PRUint32 ssl3_timeout, + const char *directory, PRBool shared) +{ + ptrdiff_t ptr; + sidCacheLock *pLock; + char *cacheMem; + PRFileMap *cacheMemMap; + char *cfn = NULL; /* cache file name */ + int locks_initialized = 0; + int locks_to_initialize = 0; + PRUint32 init_time; + + if ((!cache) || (maxCacheEntries < 0) || (!directory)) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (cache->cacheMem) { + /* Already done */ + return SECSuccess; + } + + /* make sure loser can clean up properly */ + cache->shared = shared; + cache->cacheMem = cacheMem = NULL; + cache->cacheMemMap = cacheMemMap = NULL; + cache->sharedCache = (cacheDesc *)0; + + cache->numSIDCacheLocksInitialized = 0; + cache->nextCertCacheEntry = 0; + cache->stopPolling = PR_FALSE; + cache->everInherited = PR_FALSE; + cache->poller = NULL; + cache->mutexTimeout = 0; + + cache->numSIDCacheEntries = maxCacheEntries ? maxCacheEntries + : DEF_SID_CACHE_ENTRIES; + cache->numSIDCacheSets = + SID_HOWMANY(cache->numSIDCacheEntries, SID_CACHE_ENTRIES_PER_SET); + + cache->numSIDCacheEntries = + cache->numSIDCacheSets * SID_CACHE_ENTRIES_PER_SET; + + cache->numSIDCacheLocks = + PR_MIN(cache->numSIDCacheSets, ssl_max_sid_cache_locks); + + cache->numSIDCacheSetsPerLock = + SID_HOWMANY(cache->numSIDCacheSets, cache->numSIDCacheLocks); + + cache->numCertCacheEntries = (maxCertCacheEntries > 0) ? maxCertCacheEntries + : 0; + cache->numSrvNameCacheEntries = (maxSrvNameCacheEntries >= 0) ? maxSrvNameCacheEntries + : DEF_NAME_CACHE_ENTRIES; + + /* compute size of shared memory, and offsets of all pointers */ + ptr = 0; + cache->cacheMem = (char *)ptr; + ptr += SID_ROUNDUP(sizeof(cacheDesc), SID_ALIGNMENT); + + cache->sidCacheLocks = (sidCacheLock *)ptr; + cache->keyCacheLock = cache->sidCacheLocks + cache->numSIDCacheLocks; + cache->certCacheLock = cache->keyCacheLock + 1; + cache->srvNameCacheLock = cache->certCacheLock + 1; + ptr = (ptrdiff_t)(cache->srvNameCacheLock + 1); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->sidCacheSets = (sidCacheSet *)ptr; + ptr = (ptrdiff_t)(cache->sidCacheSets + cache->numSIDCacheSets); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->sidCacheData = (sidCacheEntry *)ptr; + ptr = (ptrdiff_t)(cache->sidCacheData + cache->numSIDCacheEntries); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->certCacheData = (certCacheEntry *)ptr; + cache->sidCacheSize = + (char *)cache->certCacheData - (char *)cache->sidCacheData; + + if (cache->numCertCacheEntries < MIN_CERT_CACHE_ENTRIES) { + /* This is really a poor way to computer this! */ + cache->numCertCacheEntries = cache->sidCacheSize / sizeof(certCacheEntry); + if (cache->numCertCacheEntries < MIN_CERT_CACHE_ENTRIES) + cache->numCertCacheEntries = MIN_CERT_CACHE_ENTRIES; + } + ptr = (ptrdiff_t)(cache->certCacheData + cache->numCertCacheEntries); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->keyCacheData = (SSLWrappedSymWrappingKey *)ptr; + cache->certCacheSize = + (char *)cache->keyCacheData - (char *)cache->certCacheData; + + cache->numKeyCacheEntries = ssl_auth_size * SSL_NUM_WRAP_MECHS; + ptr = (ptrdiff_t)(cache->keyCacheData + cache->numKeyCacheEntries); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->keyCacheSize = (char *)ptr - (char *)cache->keyCacheData; + + cache->ticketKeyNameSuffix = (PRUint8 *)ptr; + ptr = (ptrdiff_t)(cache->ticketKeyNameSuffix + + SESS_TICKET_KEY_VAR_NAME_LEN); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->ticketEncKey = (encKeyCacheEntry *)ptr; + ptr = (ptrdiff_t)(cache->ticketEncKey + 1); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->ticketMacKey = (encKeyCacheEntry *)ptr; + ptr = (ptrdiff_t)(cache->ticketMacKey + 1); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->ticketKeysValid = (PRUint32 *)ptr; + ptr = (ptrdiff_t)(cache->ticketKeysValid + 1); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->srvNameCacheData = (srvNameCacheEntry *)ptr; + cache->srvNameCacheSize = + cache->numSrvNameCacheEntries * sizeof(srvNameCacheEntry); + ptr = (ptrdiff_t)(cache->srvNameCacheData + cache->numSrvNameCacheEntries); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->cacheMemSize = ptr; + + if (ssl3_timeout) { + if (ssl3_timeout > MAX_SSL3_TIMEOUT) { + ssl3_timeout = MAX_SSL3_TIMEOUT; + } + if (ssl3_timeout < MIN_SSL3_TIMEOUT) { + ssl3_timeout = MIN_SSL3_TIMEOUT; + } + cache->ssl3Timeout = ssl3_timeout; + } else { + cache->ssl3Timeout = DEF_SSL3_TIMEOUT; + } + + if (shared) { +/* Create file names */ +#if defined(XP_UNIX) || defined(XP_BEOS) + /* there's some confusion here about whether PR_OpenAnonFileMap wants + ** a directory name or a file name for its first argument. + cfn = PR_smprintf("%s/.sslsvrcache.%d", directory, myPid); + */ + cfn = PR_smprintf("%s", directory); +#elif defined(XP_WIN32) + cfn = PR_smprintf("%s/svrcache_%d_%x.ssl", directory, myPid, + GetCurrentThreadId()); +#elif defined(XP_OS2) + cfn = PR_smprintf("%s/svrcache_%d_%x.ssl", directory, myPid, + gettid()); +#else +#error "Don't know how to create file name for this platform!" +#endif + if (!cfn) { + goto loser; + } + + /* Create cache */ + cacheMemMap = PR_OpenAnonFileMap(cfn, cache->cacheMemSize, + PR_PROT_READWRITE); + + PR_smprintf_free(cfn); + if (!cacheMemMap) { + goto loser; + } + + cacheMem = PR_MemMap(cacheMemMap, 0, cache->cacheMemSize); + } else { + cacheMem = PORT_Alloc(cache->cacheMemSize); + } + + if (!cacheMem) { + goto loser; + } + + /* Initialize shared memory. This may not be necessary on all platforms */ + memset(cacheMem, 0, cache->cacheMemSize); + + /* Copy cache descriptor header into shared memory */ + memcpy(cacheMem, cache, sizeof *cache); + + /* save private copies of these values */ + cache->cacheMemMap = cacheMemMap; + cache->cacheMem = cacheMem; + cache->sharedCache = (cacheDesc *)cacheMem; + + /* Fix pointers in our private copy of cache descriptor to point to + ** spaces in shared memory + */ + cache->sidCacheLocks = (sidCacheLock *)(cache->cacheMem + (ptrdiff_t)cache->sidCacheLocks); + cache->keyCacheLock = (sidCacheLock *)(cache->cacheMem + (ptrdiff_t)cache->keyCacheLock); + cache->certCacheLock = (sidCacheLock *)(cache->cacheMem + (ptrdiff_t)cache->certCacheLock); + cache->srvNameCacheLock = (sidCacheLock *)(cache->cacheMem + (ptrdiff_t)cache->srvNameCacheLock); + cache->sidCacheSets = (sidCacheSet *)(cache->cacheMem + (ptrdiff_t)cache->sidCacheSets); + cache->sidCacheData = (sidCacheEntry *)(cache->cacheMem + (ptrdiff_t)cache->sidCacheData); + cache->certCacheData = (certCacheEntry *)(cache->cacheMem + (ptrdiff_t)cache->certCacheData); + cache->keyCacheData = (SSLWrappedSymWrappingKey *)(cache->cacheMem + (ptrdiff_t)cache->keyCacheData); + cache->ticketKeyNameSuffix = (PRUint8 *)(cache->cacheMem + (ptrdiff_t)cache->ticketKeyNameSuffix); + cache->ticketEncKey = (encKeyCacheEntry *)(cache->cacheMem + (ptrdiff_t)cache->ticketEncKey); + cache->ticketMacKey = (encKeyCacheEntry *)(cache->cacheMem + (ptrdiff_t)cache->ticketMacKey); + cache->ticketKeysValid = (PRUint32 *)(cache->cacheMem + (ptrdiff_t)cache->ticketKeysValid); + cache->srvNameCacheData = (srvNameCacheEntry *)(cache->cacheMem + (ptrdiff_t)cache->srvNameCacheData); + + /* initialize the locks */ + init_time = ssl_Time(); + pLock = cache->sidCacheLocks; + for (locks_to_initialize = cache->numSIDCacheLocks + 3; + locks_initialized < locks_to_initialize; + ++locks_initialized, ++pLock) { + + SECStatus err = sslMutex_Init(&pLock->mutex, shared); + if (err) { + cache->numSIDCacheLocksInitialized = locks_initialized; + goto loser; + } + pLock->timeStamp = init_time; + pLock->pid = 0; + } + cache->numSIDCacheLocksInitialized = locks_initialized; + + return SECSuccess; + +loser: + CloseCache(cache); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; +} + +PRUint32 +SSL_GetMaxServerCacheLocks(void) +{ + return ssl_max_sid_cache_locks + 2; + /* The extra two are the cert cache lock and the key cache lock. */ +} + +SECStatus +SSL_SetMaxServerCacheLocks(PRUint32 maxLocks) +{ + /* Minimum is 1 sid cache lock, 1 cert cache lock and 1 key cache lock. + ** We'd like to test for a maximum value, but not all platforms' header + ** files provide a symbol or function or other means of determining + ** the maximum, other than trial and error. + */ + if (maxLocks < 3) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + ssl_max_sid_cache_locks = maxLocks - 2; + /* The extra two are the cert cache lock and the key cache lock. */ + return SECSuccess; +} + +static SECStatus +ssl_ConfigServerSessionIDCacheInstanceWithOpt(cacheDesc *cache, + PRUint32 ssl3_timeout, + const char *directory, + PRBool shared, + int maxCacheEntries, + int maxCertCacheEntries, + int maxSrvNameCacheEntries) +{ + SECStatus rv; + + PORT_Assert(sizeof(sidCacheEntry) == 192); + PORT_Assert(sizeof(certCacheEntry) == 4096); + PORT_Assert(sizeof(srvNameCacheEntry) == 1072); + + rv = ssl_Init(); + if (rv != SECSuccess) { + return rv; + } + + myPid = SSL_GETPID(); + if (!directory) { + directory = DEFAULT_CACHE_DIRECTORY; + } + rv = InitCache(cache, maxCacheEntries, maxCertCacheEntries, + maxSrvNameCacheEntries, ssl3_timeout, directory, shared); + if (rv) { + return SECFailure; + } + + ssl_sid_lookup = ServerSessionIDLookup; + ssl_sid_cache = ServerSessionIDCache; + ssl_sid_uncache = ServerSessionIDUncache; + return SECSuccess; +} + +SECStatus +SSL_ConfigServerSessionIDCacheInstance(cacheDesc *cache, + int maxCacheEntries, + PRUint32 ssl2_timeout, + PRUint32 ssl3_timeout, + const char *directory, PRBool shared) +{ + return ssl_ConfigServerSessionIDCacheInstanceWithOpt(cache, + ssl3_timeout, + directory, + shared, + maxCacheEntries, + -1, -1); +} + +SECStatus +SSL_ConfigServerSessionIDCache(int maxCacheEntries, + PRUint32 ssl2_timeout, + PRUint32 ssl3_timeout, + const char *directory) +{ + ssl_InitSessionCacheLocks(PR_FALSE); + return SSL_ConfigServerSessionIDCacheInstance(&globalCache, + maxCacheEntries, ssl2_timeout, ssl3_timeout, directory, PR_FALSE); +} + +SECStatus +SSL_ShutdownServerSessionIDCacheInstance(cacheDesc *cache) +{ + CloseCache(cache); + return SECSuccess; +} + +SECStatus +SSL_ShutdownServerSessionIDCache(void) +{ +#if defined(XP_UNIX) || defined(XP_BEOS) + /* Stop the thread that polls cache for expired locks on Unix */ + StopLockPoller(&globalCache); +#endif + SSL3_ShutdownServerCache(); + return SSL_ShutdownServerSessionIDCacheInstance(&globalCache); +} + +/* Use this function, instead of SSL_ConfigServerSessionIDCache, + * if the cache will be shared by multiple processes. + */ +static SECStatus +ssl_ConfigMPServerSIDCacheWithOpt(PRUint32 ssl3_timeout, + const char *directory, + int maxCacheEntries, + int maxCertCacheEntries, + int maxSrvNameCacheEntries) +{ + char *envValue; + char *inhValue; + cacheDesc *cache = &globalCache; + PRUint32 fmStrLen; + SECStatus result; + PRStatus prStatus; + SECStatus putEnvFailed; + inheritance inherit; + char fmString[PR_FILEMAP_STRING_BUFSIZE]; + + isMultiProcess = PR_TRUE; + result = ssl_ConfigServerSessionIDCacheInstanceWithOpt(cache, + ssl3_timeout, directory, PR_TRUE, + maxCacheEntries, maxCacheEntries, maxSrvNameCacheEntries); + if (result != SECSuccess) + return result; + + prStatus = PR_ExportFileMapAsString(cache->cacheMemMap, + sizeof fmString, fmString); + if ((prStatus != PR_SUCCESS) || !(fmStrLen = strlen(fmString))) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + inherit.cacheMemSize = cache->cacheMemSize; + inherit.fmStrLen = fmStrLen; + + inhValue = BTOA_DataToAscii((unsigned char *)&inherit, sizeof inherit); + if (!inhValue || !strlen(inhValue)) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + envValue = PR_smprintf("%s,%s", inhValue, fmString); + if (!envValue || !strlen(envValue)) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + PORT_Free(inhValue); + + putEnvFailed = (SECStatus)NSS_PutEnv(envVarName, envValue); + PR_smprintf_free(envValue); + if (putEnvFailed) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + result = SECFailure; + } + +#if defined(XP_UNIX) || defined(XP_BEOS) + /* Launch thread to poll cache for expired locks on Unix */ + LaunchLockPoller(cache); +#endif + return result; +} + +/* Use this function, instead of SSL_ConfigServerSessionIDCache, + * if the cache will be shared by multiple processes. + */ +SECStatus +SSL_ConfigMPServerSIDCache(int maxCacheEntries, + PRUint32 ssl2_timeout, + PRUint32 ssl3_timeout, + const char *directory) +{ + return ssl_ConfigMPServerSIDCacheWithOpt(ssl3_timeout, + directory, + maxCacheEntries, + -1, -1); +} + +SECStatus +SSL_ConfigServerSessionIDCacheWithOpt( + PRUint32 ssl2_timeout, + PRUint32 ssl3_timeout, + const char *directory, + int maxCacheEntries, + int maxCertCacheEntries, + int maxSrvNameCacheEntries, + PRBool enableMPCache) +{ + if (!enableMPCache) { + ssl_InitSessionCacheLocks(PR_FALSE); + return ssl_ConfigServerSessionIDCacheInstanceWithOpt(&globalCache, + ssl3_timeout, directory, PR_FALSE, + maxCacheEntries, maxCertCacheEntries, maxSrvNameCacheEntries); + } else { + return ssl_ConfigMPServerSIDCacheWithOpt(ssl3_timeout, directory, + maxCacheEntries, maxCertCacheEntries, maxSrvNameCacheEntries); + } +} + +SECStatus +SSL_InheritMPServerSIDCacheInstance(cacheDesc *cache, const char *envString) +{ + unsigned char *decoString = NULL; + char *fmString = NULL; + char *myEnvString = NULL; + unsigned int decoLen; + inheritance inherit; + cacheDesc my; +#ifdef WINNT + sidCacheLock *newLocks; + int locks_initialized = 0; + int locks_to_initialize = 0; +#endif + SECStatus status = ssl_Init(); + + if (status != SECSuccess) { + return status; + } + + myPid = SSL_GETPID(); + + /* If this child was created by fork(), and not by exec() on unix, + ** then isMultiProcess will already be set. + ** If not, we'll set it below. + */ + if (isMultiProcess) { + if (cache && cache->sharedCache) { + cache->sharedCache->everInherited = PR_TRUE; + } + return SECSuccess; /* already done. */ + } + + ssl_InitSessionCacheLocks(PR_FALSE); + + ssl_sid_lookup = ServerSessionIDLookup; + ssl_sid_cache = ServerSessionIDCache; + ssl_sid_uncache = ServerSessionIDUncache; + + if (!envString) { + envString = PR_GetEnvSecure(envVarName); + if (!envString) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + } + myEnvString = PORT_Strdup(envString); + if (!myEnvString) + return SECFailure; + fmString = strchr(myEnvString, ','); + if (!fmString) + goto loser; + *fmString++ = 0; + + decoString = ATOB_AsciiToData(myEnvString, &decoLen); + if (!decoString) { + goto loser; + } + if (decoLen != sizeof inherit) { + goto loser; + } + + PORT_Memcpy(&inherit, decoString, sizeof inherit); + + if (strlen(fmString) != inherit.fmStrLen) { + goto loser; + } + + memset(cache, 0, sizeof *cache); + cache->cacheMemSize = inherit.cacheMemSize; + + /* Create cache */ + cache->cacheMemMap = PR_ImportFileMapFromString(fmString); + if (!cache->cacheMemMap) { + goto loser; + } + cache->cacheMem = PR_MemMap(cache->cacheMemMap, 0, cache->cacheMemSize); + if (!cache->cacheMem) { + goto loser; + } + cache->sharedCache = (cacheDesc *)cache->cacheMem; + + if (cache->sharedCache->cacheMemSize != cache->cacheMemSize) { + goto loser; + } + + /* We're now going to overwrite the local cache instance with the + ** shared copy of the cache struct, then update several values in + ** the local cache using the values for cache->cacheMemMap and + ** cache->cacheMem computed just above. So, we copy cache into + ** the automatic variable "my", to preserve the variables while + ** cache is overwritten. + */ + my = *cache; /* save values computed above. */ + memcpy(cache, cache->sharedCache, sizeof *cache); /* overwrite */ + + /* Fix pointers in our private copy of cache descriptor to point to + ** spaces in shared memory, whose address is now in "my". + */ + cache->sidCacheLocks = (sidCacheLock *)(my.cacheMem + (ptrdiff_t)cache->sidCacheLocks); + cache->keyCacheLock = (sidCacheLock *)(my.cacheMem + (ptrdiff_t)cache->keyCacheLock); + cache->certCacheLock = (sidCacheLock *)(my.cacheMem + (ptrdiff_t)cache->certCacheLock); + cache->srvNameCacheLock = (sidCacheLock *)(my.cacheMem + (ptrdiff_t)cache->srvNameCacheLock); + cache->sidCacheSets = (sidCacheSet *)(my.cacheMem + (ptrdiff_t)cache->sidCacheSets); + cache->sidCacheData = (sidCacheEntry *)(my.cacheMem + (ptrdiff_t)cache->sidCacheData); + cache->certCacheData = (certCacheEntry *)(my.cacheMem + (ptrdiff_t)cache->certCacheData); + cache->keyCacheData = (SSLWrappedSymWrappingKey *)(my.cacheMem + (ptrdiff_t)cache->keyCacheData); + cache->ticketKeyNameSuffix = (PRUint8 *)(my.cacheMem + (ptrdiff_t)cache->ticketKeyNameSuffix); + cache->ticketEncKey = (encKeyCacheEntry *)(my.cacheMem + (ptrdiff_t)cache->ticketEncKey); + cache->ticketMacKey = (encKeyCacheEntry *)(my.cacheMem + (ptrdiff_t)cache->ticketMacKey); + cache->ticketKeysValid = (PRUint32 *)(my.cacheMem + (ptrdiff_t)cache->ticketKeysValid); + cache->srvNameCacheData = (srvNameCacheEntry *)(my.cacheMem + (ptrdiff_t)cache->srvNameCacheData); + + cache->cacheMemMap = my.cacheMemMap; + cache->cacheMem = my.cacheMem; + cache->sharedCache = (cacheDesc *)cache->cacheMem; + +#ifdef WINNT + /* On Windows NT we need to "fix" the sidCacheLocks here to support fibers + ** When NT fibers are used in a multi-process server, a second level of + ** locking is needed to prevent a deadlock, in case a fiber acquires the + ** cross-process mutex, yields, and another fiber is later scheduled on + ** the same native thread and tries to acquire the cross-process mutex. + ** We do this by using a PRLock in the sslMutex. However, it is stored in + ** shared memory as part of sidCacheLocks, and we don't want to overwrite + ** the PRLock of the parent process. So we need to make new, private + ** copies of sidCacheLocks before modifying the sslMutex with our own + ** PRLock + */ + + /* note from jpierre : this should be free'd in child processes when + ** a function is added to delete the SSL session cache in the future. + */ + locks_to_initialize = cache->numSIDCacheLocks + 3; + newLocks = PORT_NewArray(sidCacheLock, locks_to_initialize); + if (!newLocks) + goto loser; + /* copy the old locks */ + memcpy(newLocks, cache->sidCacheLocks, + locks_to_initialize * sizeof(sidCacheLock)); + cache->sidCacheLocks = newLocks; + /* fix the locks */ + for (; locks_initialized < locks_to_initialize; ++locks_initialized) { + /* now, make a local PRLock in this sslMutex for this child process */ + SECStatus err; + err = sslMutex_2LevelInit(&newLocks[locks_initialized].mutex); + if (err != SECSuccess) { + cache->numSIDCacheLocksInitialized = locks_initialized; + goto loser; + } + } + cache->numSIDCacheLocksInitialized = locks_initialized; + + /* also fix the key and cert cache which use the last 2 lock entries */ + cache->keyCacheLock = cache->sidCacheLocks + cache->numSIDCacheLocks; + cache->certCacheLock = cache->keyCacheLock + 1; + cache->srvNameCacheLock = cache->certCacheLock + 1; +#endif + + PORT_Free(myEnvString); + PORT_Free(decoString); + + /* mark that we have inherited this. */ + cache->sharedCache->everInherited = PR_TRUE; + isMultiProcess = PR_TRUE; + + return SECSuccess; + +loser: + PORT_Free(myEnvString); + if (decoString) + PORT_Free(decoString); + CloseCache(cache); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; +} + +SECStatus +SSL_InheritMPServerSIDCache(const char *envString) +{ + return SSL_InheritMPServerSIDCacheInstance(&globalCache, envString); +} + +#if defined(XP_UNIX) || defined(XP_BEOS) + +#define SID_LOCK_EXPIRATION_TIMEOUT 30 /* seconds */ + +static void +LockPoller(void *arg) +{ + cacheDesc *cache = (cacheDesc *)arg; + cacheDesc *sharedCache = cache->sharedCache; + sidCacheLock *pLock; + PRIntervalTime timeout; + PRUint32 now; + PRUint32 then; + int locks_polled = 0; + int locks_to_poll = cache->numSIDCacheLocks + 2; + PRUint32 expiration = cache->mutexTimeout; + + timeout = PR_SecondsToInterval(expiration); + while (!sharedCache->stopPolling) { + PR_Sleep(timeout); + if (sharedCache->stopPolling) + break; + + now = ssl_Time(); + then = now - expiration; + for (pLock = cache->sidCacheLocks, locks_polled = 0; + locks_to_poll > locks_polled && !sharedCache->stopPolling; + ++locks_polled, ++pLock) { + pid_t pid; + + if (pLock->timeStamp < then && + pLock->timeStamp != 0 && + (pid = pLock->pid) != 0) { + + /* maybe we should try the lock? */ + int result = kill(pid, 0); + if (result < 0 && errno == ESRCH) { + SECStatus rv; + /* No process exists by that pid any more. + ** Treat this mutex as abandoned. + */ + pLock->timeStamp = now; + pLock->pid = 0; + rv = sslMutex_Unlock(&pLock->mutex); + if (rv != SECSuccess) { + /* Now what? */ + } + } + } + } /* end of loop over locks */ + } /* end of entire polling loop */ +} + +/* Launch thread to poll cache for expired locks */ +static SECStatus +LaunchLockPoller(cacheDesc *cache) +{ + const char *timeoutString; + PRThread *pollerThread; + + cache->mutexTimeout = SID_LOCK_EXPIRATION_TIMEOUT; + timeoutString = PR_GetEnvSecure("NSS_SSL_SERVER_CACHE_MUTEX_TIMEOUT"); + if (timeoutString) { + long newTime = strtol(timeoutString, 0, 0); + if (newTime == 0) + return SECSuccess; /* application doesn't want poller thread */ + if (newTime > 0) + cache->mutexTimeout = (PRUint32)newTime; + /* if error (newTime < 0) ignore it and use default */ + } + + pollerThread = + PR_CreateThread(PR_USER_THREAD, LockPoller, cache, PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); + if (!pollerThread) { + return SECFailure; + } + cache->poller = pollerThread; + return SECSuccess; +} + +/* Stop the thread that polls cache for expired locks */ +static SECStatus +StopLockPoller(cacheDesc *cache) +{ + if (!cache->poller) { + return SECSuccess; + } + cache->sharedCache->stopPolling = PR_TRUE; + if (PR_Interrupt(cache->poller) != PR_SUCCESS) { + return SECFailure; + } + if (PR_JoinThread(cache->poller) != PR_SUCCESS) { + return SECFailure; + } + cache->poller = NULL; + return SECSuccess; +} +#endif + +/************************************************************************ + * Code dealing with shared wrapped symmetric wrapping keys below * + ************************************************************************/ + +/* If now is zero, it implies that the lock is not held, and must be +** aquired here. +*/ +static PRBool +getSvrWrappingKey(PRInt32 symWrapMechIndex, + SSLAuthType authType, + SSLWrappedSymWrappingKey *wswk, + cacheDesc *cache, + PRUint32 lockTime) +{ + PRUint32 ndx = (authType * SSL_NUM_WRAP_MECHS) + symWrapMechIndex; + SSLWrappedSymWrappingKey *pwswk = cache->keyCacheData + ndx; + PRUint32 now = 0; + PRBool rv = PR_FALSE; + + if (!cache->cacheMem) { /* cache is uninitialized */ + PORT_SetError(SSL_ERROR_SERVER_CACHE_NOT_CONFIGURED); + return rv; + } + if (!lockTime) { + lockTime = now = LockSidCacheLock(cache->keyCacheLock, now); + if (!lockTime) { + return rv; + } + } + if (pwswk->authType == authType && + pwswk->symWrapMechIndex == symWrapMechIndex && + pwswk->wrappedSymKeyLen != 0) { + *wswk = *pwswk; + rv = PR_TRUE; + } + if (now) { + UnlockSidCacheLock(cache->keyCacheLock); + } + return rv; +} + +PRBool +ssl_GetWrappingKey(PRInt32 symWrapMechIndex, + SSLAuthType authType, + SSLWrappedSymWrappingKey *wswk) +{ + PRBool rv; + + PORT_Assert((unsigned)authType < ssl_auth_size); + PORT_Assert((unsigned)symWrapMechIndex < SSL_NUM_WRAP_MECHS); + if ((unsigned)authType < ssl_auth_size && + (unsigned)symWrapMechIndex < SSL_NUM_WRAP_MECHS) { + rv = getSvrWrappingKey(symWrapMechIndex, authType, wswk, + &globalCache, 0); + } else { + rv = PR_FALSE; + } + + return rv; +} + +/* Wrap and cache a session ticket key. */ +static PRBool +WrapTicketKey(SECKEYPublicKey *svrPubKey, PK11SymKey *symKey, + const char *keyName, encKeyCacheEntry *cacheEntry) +{ + SECItem wrappedKey = { siBuffer, NULL, 0 }; + + wrappedKey.len = SECKEY_PublicKeyStrength(svrPubKey); + PORT_Assert(wrappedKey.len <= sizeof(cacheEntry->bytes)); + if (wrappedKey.len > sizeof(cacheEntry->bytes)) + return PR_FALSE; + wrappedKey.data = cacheEntry->bytes; + + if (PK11_PubWrapSymKey(CKM_RSA_PKCS, svrPubKey, symKey, &wrappedKey) != + SECSuccess) { + SSL_DBG(("%d: SSL[%s]: Unable to wrap session ticket %s.", + SSL_GETPID(), "unknown", keyName)); + return PR_FALSE; + } + cacheEntry->length = wrappedKey.len; + return PR_TRUE; +} + +static PRBool +GenerateTicketKeys(void *pwArg, unsigned char *keyName, PK11SymKey **aesKey, + PK11SymKey **macKey) +{ + PK11SlotInfo *slot; + CK_MECHANISM_TYPE mechanismArray[2]; + PK11SymKey *aesKeyTmp = NULL; + PK11SymKey *macKeyTmp = NULL; + cacheDesc *cache = &globalCache; + PRUint8 ticketKeyNameSuffixLocal[SESS_TICKET_KEY_VAR_NAME_LEN]; + PRUint8 *ticketKeyNameSuffix; + + if (!cache->cacheMem) { + /* cache is not initalized. Use stack buffer */ + ticketKeyNameSuffix = ticketKeyNameSuffixLocal; + } else { + ticketKeyNameSuffix = cache->ticketKeyNameSuffix; + } + + if (PK11_GenerateRandom(ticketKeyNameSuffix, + SESS_TICKET_KEY_VAR_NAME_LEN) != + SECSuccess) { + SSL_DBG(("%d: SSL[%s]: Unable to generate random key name bytes.", + SSL_GETPID(), "unknown")); + goto loser; + } + + mechanismArray[0] = CKM_AES_CBC; + mechanismArray[1] = CKM_SHA256_HMAC; + + slot = PK11_GetBestSlotMultiple(mechanismArray, 2, pwArg); + if (slot) { + aesKeyTmp = PK11_KeyGen(slot, mechanismArray[0], NULL, + AES_256_KEY_LENGTH, pwArg); + macKeyTmp = PK11_KeyGen(slot, mechanismArray[1], NULL, + SHA256_LENGTH, pwArg); + PK11_FreeSlot(slot); + } + + if (aesKeyTmp == NULL || macKeyTmp == NULL) { + SSL_DBG(("%d: SSL[%s]: Unable to generate session ticket keys.", + SSL_GETPID(), "unknown")); + goto loser; + } + PORT_Memcpy(keyName, ticketKeyNameSuffix, SESS_TICKET_KEY_VAR_NAME_LEN); + *aesKey = aesKeyTmp; + *macKey = macKeyTmp; + return PR_TRUE; + +loser: + if (aesKeyTmp) + PK11_FreeSymKey(aesKeyTmp); + if (macKeyTmp) + PK11_FreeSymKey(macKeyTmp); + return PR_FALSE; +} + +static PRBool +GenerateAndWrapTicketKeys(SECKEYPublicKey *svrPubKey, void *pwArg, + unsigned char *keyName, PK11SymKey **aesKey, + PK11SymKey **macKey) +{ + PK11SymKey *aesKeyTmp = NULL; + PK11SymKey *macKeyTmp = NULL; + cacheDesc *cache = &globalCache; + + if (!GenerateTicketKeys(pwArg, keyName, &aesKeyTmp, &macKeyTmp)) { + goto loser; + } + + if (cache->cacheMem) { + /* Export the keys to the shared cache in wrapped form. */ + if (!WrapTicketKey(svrPubKey, aesKeyTmp, "enc key", cache->ticketEncKey)) + goto loser; + if (!WrapTicketKey(svrPubKey, macKeyTmp, "mac key", cache->ticketMacKey)) + goto loser; + } + *aesKey = aesKeyTmp; + *macKey = macKeyTmp; + return PR_TRUE; + +loser: + if (aesKeyTmp) + PK11_FreeSymKey(aesKeyTmp); + if (macKeyTmp) + PK11_FreeSymKey(macKeyTmp); + return PR_FALSE; +} + +static PRBool +UnwrapCachedTicketKeys(SECKEYPrivateKey *svrPrivKey, unsigned char *keyName, + PK11SymKey **aesKey, PK11SymKey **macKey) +{ + SECItem wrappedKey = { siBuffer, NULL, 0 }; + PK11SymKey *aesKeyTmp = NULL; + PK11SymKey *macKeyTmp = NULL; + cacheDesc *cache = &globalCache; + + wrappedKey.data = cache->ticketEncKey->bytes; + wrappedKey.len = cache->ticketEncKey->length; + PORT_Assert(wrappedKey.len <= sizeof(cache->ticketEncKey->bytes)); + aesKeyTmp = PK11_PubUnwrapSymKey(svrPrivKey, &wrappedKey, + CKM_AES_CBC, CKA_DECRYPT, 0); + + wrappedKey.data = cache->ticketMacKey->bytes; + wrappedKey.len = cache->ticketMacKey->length; + PORT_Assert(wrappedKey.len <= sizeof(cache->ticketMacKey->bytes)); + macKeyTmp = PK11_PubUnwrapSymKey(svrPrivKey, &wrappedKey, + CKM_SHA256_HMAC, CKA_SIGN, 0); + + if (aesKeyTmp == NULL || macKeyTmp == NULL) { + SSL_DBG(("%d: SSL[%s]: Unable to unwrap session ticket keys.", + SSL_GETPID(), "unknown")); + goto loser; + } + SSL_DBG(("%d: SSL[%s]: Successfully unwrapped session ticket keys.", + SSL_GETPID(), "unknown")); + + PORT_Memcpy(keyName, cache->ticketKeyNameSuffix, + SESS_TICKET_KEY_VAR_NAME_LEN); + *aesKey = aesKeyTmp; + *macKey = macKeyTmp; + return PR_TRUE; + +loser: + if (aesKeyTmp) + PK11_FreeSymKey(aesKeyTmp); + if (macKeyTmp) + PK11_FreeSymKey(macKeyTmp); + return PR_FALSE; +} + +PRBool +ssl_GetSessionTicketKeys(SECKEYPrivateKey *svrPrivKey, + SECKEYPublicKey *svrPubKey, void *pwArg, + unsigned char *keyName, PK11SymKey **aesKey, + PK11SymKey **macKey) +{ + PRUint32 now = 0; + PRBool rv = PR_FALSE; + PRBool keysGenerated = PR_FALSE; + cacheDesc *cache = &globalCache; + + if (!cache->cacheMem) { + /* cache is uninitialized. Generate keys and return them + * without caching. */ + return GenerateTicketKeys(pwArg, keyName, aesKey, macKey); + } + + now = LockSidCacheLock(cache->keyCacheLock, now); + if (!now) + return rv; + + if (!*(cache->ticketKeysValid)) { + /* Keys do not exist, create them. */ + if (!GenerateAndWrapTicketKeys(svrPubKey, pwArg, keyName, + aesKey, macKey)) + goto loser; + keysGenerated = PR_TRUE; + *(cache->ticketKeysValid) = 1; + } + + rv = PR_TRUE; + +loser: + UnlockSidCacheLock(cache->keyCacheLock); + if (rv && !keysGenerated) + rv = UnwrapCachedTicketKeys(svrPrivKey, keyName, aesKey, macKey); + return rv; +} + +/* The caller passes in the new value it wants + * to set. This code tests the wrapped sym key entry in the shared memory. + * If it is uninitialized, this function writes the caller's value into + * the disk entry, and returns false. + * Otherwise, it overwrites the caller's wswk with the value obtained from + * the disk, and returns PR_TRUE. + * This is all done while holding the locks/mutexes necessary to make + * the operation atomic. + */ +PRBool +ssl_SetWrappingKey(SSLWrappedSymWrappingKey *wswk) +{ + cacheDesc *cache = &globalCache; + PRBool rv = PR_FALSE; + SSLAuthType authType = wswk->authType; + /* type of keys used to wrap SymWrapKey*/ + PRInt32 symWrapMechIndex = wswk->symWrapMechIndex; + PRUint32 ndx; + PRUint32 now = 0; + SSLWrappedSymWrappingKey myWswk; + + if (!cache->cacheMem) { /* cache is uninitialized */ + PORT_SetError(SSL_ERROR_SERVER_CACHE_NOT_CONFIGURED); + return 0; + } + + PORT_Assert((unsigned)authType < ssl_auth_size); + if ((unsigned)authType >= ssl_auth_size) + return 0; + + PORT_Assert((unsigned)symWrapMechIndex < SSL_NUM_WRAP_MECHS); + if ((unsigned)symWrapMechIndex >= SSL_NUM_WRAP_MECHS) + return 0; + + ndx = (authType * SSL_NUM_WRAP_MECHS) + symWrapMechIndex; + PORT_Memset(&myWswk, 0, sizeof myWswk); /* eliminate UMRs. */ + + now = LockSidCacheLock(cache->keyCacheLock, now); + if (now) { + rv = getSvrWrappingKey(wswk->symWrapMechIndex, wswk->authType, + &myWswk, cache, now); + if (rv) { + /* we found it on disk, copy it out to the caller. */ + PORT_Memcpy(wswk, &myWswk, sizeof *wswk); + } else { + /* Wasn't on disk, and we're still holding the lock, so write it. */ + cache->keyCacheData[ndx] = *wswk; + } + UnlockSidCacheLock(cache->keyCacheLock); + } + return rv; +} + +#else /* MAC version or other platform */ + +#include "seccomon.h" +#include "cert.h" +#include "ssl.h" +#include "sslimpl.h" + +SECStatus +SSL_ConfigServerSessionIDCache(int maxCacheEntries, + PRUint32 ssl2_timeout, + PRUint32 ssl3_timeout, + const char *directory) +{ + PR_ASSERT(!"SSL servers are not supported on this platform. (SSL_ConfigServerSessionIDCache)"); + return SECFailure; +} + +SECStatus +SSL_ConfigMPServerSIDCache(int maxCacheEntries, + PRUint32 ssl2_timeout, + PRUint32 ssl3_timeout, + const char *directory) +{ + PR_ASSERT(!"SSL servers are not supported on this platform. (SSL_ConfigMPServerSIDCache)"); + return SECFailure; +} + +SECStatus +SSL_InheritMPServerSIDCache(const char *envString) +{ + PR_ASSERT(!"SSL servers are not supported on this platform. (SSL_InheritMPServerSIDCache)"); + return SECFailure; +} + +PRBool +ssl_GetWrappingKey(PRInt32 symWrapMechIndex, + SSLAuthType authType, + SSLWrappedSymWrappingKey *wswk) +{ + PRBool rv = PR_FALSE; + PR_ASSERT(!"SSL servers are not supported on this platform. (ssl_GetWrappingKey)"); + return rv; +} + +/* This is a kind of test-and-set. The caller passes in the new value it wants + * to set. This code tests the wrapped sym key entry in the shared memory. + * If it is uninitialized, this function writes the caller's value into + * the disk entry, and returns false. + * Otherwise, it overwrites the caller's wswk with the value obtained from + * the disk, and returns PR_TRUE. + * This is all done while holding the locks/mutexes necessary to make + * the operation atomic. + */ +PRBool +ssl_SetWrappingKey(SSLWrappedSymWrappingKey *wswk) +{ + PRBool rv = PR_FALSE; + PR_ASSERT(!"SSL servers are not supported on this platform. (ssl_SetWrappingKey)"); + return rv; +} + +PRUint32 +SSL_GetMaxServerCacheLocks(void) +{ + PR_ASSERT(!"SSL servers are not supported on this platform. (SSL_GetMaxServerCacheLocks)"); + return -1; +} + +SECStatus +SSL_SetMaxServerCacheLocks(PRUint32 maxLocks) +{ + PR_ASSERT(!"SSL servers are not supported on this platform. (SSL_SetMaxServerCacheLocks)"); + return SECFailure; +} + +#endif /* XP_UNIX || XP_WIN32 */ |