/* * SSL v2 handshake functions, and functions common to SSL2 and SSL3. * * 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/. */ /* $Id$ */ #include "nssrenam.h" #include "cert.h" #include "secitem.h" #include "sechash.h" #include "cryptohi.h" /* for SGN_ funcs */ #include "keyhi.h" /* for SECKEY_ high level functions. */ #include "ssl.h" #include "sslimpl.h" #include "sslproto.h" #include "ssl3prot.h" #include "sslerr.h" #include "pk11func.h" #include "prinit.h" #include "prtime.h" /* for PR_Now() */ #define XXX static PRBool policyWasSet; /* This ordered list is indexed by (SSL_CK_xx * 3) */ /* Second and third bytes are MSB and LSB of master key length. */ static const PRUint8 allCipherSuites[] = { 0, 0, 0, SSL_CK_RC4_128_WITH_MD5, 0x00, 0x80, SSL_CK_RC4_128_EXPORT40_WITH_MD5, 0x00, 0x80, SSL_CK_RC2_128_CBC_WITH_MD5, 0x00, 0x80, SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5, 0x00, 0x80, SSL_CK_IDEA_128_CBC_WITH_MD5, 0x00, 0x80, SSL_CK_DES_64_CBC_WITH_MD5, 0x00, 0x40, SSL_CK_DES_192_EDE3_CBC_WITH_MD5, 0x00, 0xC0, 0, 0, 0 }; #define ssl2_NUM_SUITES_IMPLEMENTED 6 /* This list is sent back to the client when the client-hello message * contains no overlapping ciphers, so the client can report what ciphers * are supported by the server. Unlike allCipherSuites (above), this list * is sorted by descending preference, not by cipherSuite number. */ static const PRUint8 implementedCipherSuites[ssl2_NUM_SUITES_IMPLEMENTED * 3] = { SSL_CK_RC4_128_WITH_MD5, 0x00, 0x80, SSL_CK_RC2_128_CBC_WITH_MD5, 0x00, 0x80, SSL_CK_DES_192_EDE3_CBC_WITH_MD5, 0x00, 0xC0, SSL_CK_DES_64_CBC_WITH_MD5, 0x00, 0x40, SSL_CK_RC4_128_EXPORT40_WITH_MD5, 0x00, 0x80, SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5, 0x00, 0x80 }; typedef struct ssl2SpecsStr { PRUint8 nkm; /* do this many hashes to generate key material. */ PRUint8 nkd; /* size of readKey and writeKey in bytes. */ PRUint8 blockSize; PRUint8 blockShift; CK_MECHANISM_TYPE mechanism; PRUint8 keyLen; /* cipher symkey size in bytes. */ PRUint8 pubLen; /* publicly reveal this many bytes of key. */ PRUint8 ivLen; /* length of IV data at *ca. */ } ssl2Specs; static const ssl2Specs ssl_Specs[] = { /* NONE */ { 0, 0, 0, 0, }, /* SSL_CK_RC4_128_WITH_MD5 */ { 2, 16, 1, 0, CKM_RC4, 16, 0, 0, }, /* SSL_CK_RC4_128_EXPORT40_WITH_MD5 */ { 2, 16, 1, 0, CKM_RC4, 16, 11, 0, }, /* SSL_CK_RC2_128_CBC_WITH_MD5 */ { 2, 16, 8, 3, CKM_RC2_CBC, 16, 0, 8, }, /* SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5 */ { 2, 16, 8, 3, CKM_RC2_CBC, 16, 11, 8, }, /* SSL_CK_IDEA_128_CBC_WITH_MD5 */ { 0, 0, 0, 0, }, /* SSL_CK_DES_64_CBC_WITH_MD5 */ { 1, 8, 8, 3, CKM_DES_CBC, 8, 0, 8, }, /* SSL_CK_DES_192_EDE3_CBC_WITH_MD5 */ { 3, 24, 8, 3, CKM_DES3_CBC, 24, 0, 8, }, }; #define SET_ERROR_CODE /* reminder */ #define TEST_FOR_FAILURE /* reminder */ /* ** Put a string tag in the library so that we can examine an executable ** and see what kind of security it supports. */ const char *ssl_version = "SECURITY_VERSION:" " +us" " +export" #ifdef TRACE " +trace" #endif #ifdef DEBUG " +debug" #endif ; const char * const ssl_cipherName[] = { "unknown", "RC4", "RC4-Export", "RC2-CBC", "RC2-CBC-Export", "IDEA-CBC", "DES-CBC", "DES-EDE3-CBC", "unknown", "unknown", /* was fortezza, NO LONGER USED */ }; /* bit-masks, showing which SSLv2 suites are allowed. * lsb corresponds to first cipher suite in allCipherSuites[]. */ static PRUint16 allowedByPolicy; /* all off by default */ static PRUint16 maybeAllowedByPolicy; /* all off by default */ static PRUint16 chosenPreference = 0xff; /* all on by default */ /* bit values for the above two bit masks */ #define SSL_CB_RC4_128_WITH_MD5 (1 << SSL_CK_RC4_128_WITH_MD5) #define SSL_CB_RC4_128_EXPORT40_WITH_MD5 (1 << SSL_CK_RC4_128_EXPORT40_WITH_MD5) #define SSL_CB_RC2_128_CBC_WITH_MD5 (1 << SSL_CK_RC2_128_CBC_WITH_MD5) #define SSL_CB_RC2_128_CBC_EXPORT40_WITH_MD5 (1 << SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5) #define SSL_CB_IDEA_128_CBC_WITH_MD5 (1 << SSL_CK_IDEA_128_CBC_WITH_MD5) #define SSL_CB_DES_64_CBC_WITH_MD5 (1 << SSL_CK_DES_64_CBC_WITH_MD5) #define SSL_CB_DES_192_EDE3_CBC_WITH_MD5 (1 << SSL_CK_DES_192_EDE3_CBC_WITH_MD5) #define SSL_CB_IMPLEMENTED \ (SSL_CB_RC4_128_WITH_MD5 | \ SSL_CB_RC4_128_EXPORT40_WITH_MD5 | \ SSL_CB_RC2_128_CBC_WITH_MD5 | \ SSL_CB_RC2_128_CBC_EXPORT40_WITH_MD5 | \ SSL_CB_DES_64_CBC_WITH_MD5 | \ SSL_CB_DES_192_EDE3_CBC_WITH_MD5) /* Construct a socket's list of cipher specs from the global default values. */ static SECStatus ssl2_ConstructCipherSpecs(sslSocket *ss) { PRUint8 * cs = NULL; unsigned int allowed; unsigned int count; int ssl3_count = 0; int final_count; int i; SECStatus rv; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); count = 0; PORT_Assert(ss != 0); allowed = !ss->opt.enableSSL2 ? 0 : (ss->allowedByPolicy & ss->chosenPreference & SSL_CB_IMPLEMENTED); while (allowed) { if (allowed & 1) ++count; allowed >>= 1; } /* Call ssl3_config_match_init() once here, * instead of inside ssl3_ConstructV2CipherSpecsHack(), * because the latter gets called twice below, * and then again in ssl2_BeginClientHandshake(). */ ssl3_config_match_init(ss); /* ask SSL3 how many cipher suites it has. */ rv = ssl3_ConstructV2CipherSpecsHack(ss, NULL, &ssl3_count); if (rv < 0) return rv; count += ssl3_count; /* Allocate memory to hold cipher specs */ if (count > 0) cs = (PRUint8*) PORT_Alloc(count * 3); else PORT_SetError(SSL_ERROR_SSL_DISABLED); if (cs == NULL) return SECFailure; if (ss->cipherSpecs != NULL) { PORT_Free(ss->cipherSpecs); } ss->cipherSpecs = cs; ss->sizeCipherSpecs = count * 3; /* fill in cipher specs for SSL2 cipher suites */ allowed = !ss->opt.enableSSL2 ? 0 : (ss->allowedByPolicy & ss->chosenPreference & SSL_CB_IMPLEMENTED); for (i = 0; i < ssl2_NUM_SUITES_IMPLEMENTED * 3; i += 3) { const PRUint8 * hs = implementedCipherSuites + i; int ok = allowed & (1U << hs[0]); if (ok) { cs[0] = hs[0]; cs[1] = hs[1]; cs[2] = hs[2]; cs += 3; } } /* now have SSL3 add its suites onto the end */ rv = ssl3_ConstructV2CipherSpecsHack(ss, cs, &final_count); /* adjust for any difference between first pass and second pass */ ss->sizeCipherSpecs -= (ssl3_count - final_count) * 3; return rv; } /* This function is called immediately after ssl2_ConstructCipherSpecs() ** at the beginning of a handshake. It detects cases where a protocol ** (e.g. SSL2 or SSL3) is logically enabled, but all its cipher suites ** for that protocol have been disabled. If such cases, it clears the ** enable bit for the protocol. If no protocols remain enabled, or ** if no cipher suites are found, it sets the error code and returns ** SECFailure, otherwise it returns SECSuccess. */ static SECStatus ssl2_CheckConfigSanity(sslSocket *ss) { unsigned int allowed; int ssl3CipherCount = 0; SECStatus rv; /* count the SSL2 and SSL3 enabled ciphers. * if either is zero, clear the socket's enable for that protocol. */ if (!ss->cipherSpecs) goto disabled; allowed = ss->allowedByPolicy & ss->chosenPreference; if (! allowed) ss->opt.enableSSL2 = PR_FALSE; /* not really enabled if no ciphers */ /* ssl3_config_match_init was called in ssl2_ConstructCipherSpecs(). */ /* Ask how many ssl3 CipherSuites were enabled. */ rv = ssl3_ConstructV2CipherSpecsHack(ss, NULL, &ssl3CipherCount); if (rv != SECSuccess || ssl3CipherCount <= 0) { /* SSL3/TLS not really enabled if no ciphers */ ss->vrange.min = SSL_LIBRARY_VERSION_NONE; ss->vrange.max = SSL_LIBRARY_VERSION_NONE; } if (!ss->opt.enableSSL2 && SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) { SSL_DBG(("%d: SSL[%d]: Can't handshake! all versions disabled.", SSL_GETPID(), ss->fd)); disabled: PORT_SetError(SSL_ERROR_SSL_DISABLED); return SECFailure; } return SECSuccess; } /* * Since this is a global (not per-socket) setting, we cannot use the * HandshakeLock to protect this. Probably want a global lock. */ SECStatus ssl2_SetPolicy(PRInt32 which, PRInt32 policy) { PRUint32 bitMask; SECStatus rv = SECSuccess; which &= 0x000f; bitMask = 1 << which; if (!(bitMask & SSL_CB_IMPLEMENTED)) { PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE); return SECFailure; } if (policy == SSL_ALLOWED) { allowedByPolicy |= bitMask; maybeAllowedByPolicy |= bitMask; } else if (policy == SSL_RESTRICTED) { allowedByPolicy &= ~bitMask; maybeAllowedByPolicy |= bitMask; } else { allowedByPolicy &= ~bitMask; maybeAllowedByPolicy &= ~bitMask; } allowedByPolicy &= SSL_CB_IMPLEMENTED; maybeAllowedByPolicy &= SSL_CB_IMPLEMENTED; policyWasSet = PR_TRUE; return rv; } SECStatus ssl2_GetPolicy(PRInt32 which, PRInt32 *oPolicy) { PRUint32 bitMask; PRInt32 policy; which &= 0x000f; bitMask = 1 << which; /* Caller assures oPolicy is not null. */ if (!(bitMask & SSL_CB_IMPLEMENTED)) { PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE); *oPolicy = SSL_NOT_ALLOWED; return SECFailure; } if (maybeAllowedByPolicy & bitMask) { policy = (allowedByPolicy & bitMask) ? SSL_ALLOWED : SSL_RESTRICTED; } else { policy = SSL_NOT_ALLOWED; } *oPolicy = policy; return SECSuccess; } /* * Since this is a global (not per-socket) setting, we cannot use the * HandshakeLock to protect this. Probably want a global lock. * Called from SSL_CipherPrefSetDefault in sslsock.c * These changes have no effect on any sslSockets already created. */ SECStatus ssl2_CipherPrefSetDefault(PRInt32 which, PRBool enabled) { PRUint32 bitMask; which &= 0x000f; bitMask = 1 << which; if (!(bitMask & SSL_CB_IMPLEMENTED)) { PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE); return SECFailure; } if (enabled) chosenPreference |= bitMask; else chosenPreference &= ~bitMask; chosenPreference &= SSL_CB_IMPLEMENTED; return SECSuccess; } SECStatus ssl2_CipherPrefGetDefault(PRInt32 which, PRBool *enabled) { PRBool rv = PR_FALSE; PRUint32 bitMask; which &= 0x000f; bitMask = 1 << which; if (!(bitMask & SSL_CB_IMPLEMENTED)) { PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE); *enabled = PR_FALSE; return SECFailure; } rv = (PRBool)((chosenPreference & bitMask) != 0); *enabled = rv; return SECSuccess; } SECStatus ssl2_CipherPrefSet(sslSocket *ss, PRInt32 which, PRBool enabled) { PRUint32 bitMask; which &= 0x000f; bitMask = 1 << which; if (!(bitMask & SSL_CB_IMPLEMENTED)) { PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE); return SECFailure; } if (enabled) ss->chosenPreference |= bitMask; else ss->chosenPreference &= ~bitMask; ss->chosenPreference &= SSL_CB_IMPLEMENTED; return SECSuccess; } SECStatus ssl2_CipherPrefGet(sslSocket *ss, PRInt32 which, PRBool *enabled) { PRBool rv = PR_FALSE; PRUint32 bitMask; which &= 0x000f; bitMask = 1 << which; if (!(bitMask & SSL_CB_IMPLEMENTED)) { PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE); *enabled = PR_FALSE; return SECFailure; } rv = (PRBool)((ss->chosenPreference & bitMask) != 0); *enabled = rv; return SECSuccess; } /* copy global default policy into socket. */ void ssl2_InitSocketPolicy(sslSocket *ss) { ss->allowedByPolicy = allowedByPolicy; ss->maybeAllowedByPolicy = maybeAllowedByPolicy; ss->chosenPreference = chosenPreference; } /************************************************************************/ /* Called from ssl2_CreateSessionCypher(), which already holds handshake lock. */ static SECStatus ssl2_CreateMAC(sslSecurityInfo *sec, SECItem *readKey, SECItem *writeKey, int cipherChoice) { switch (cipherChoice) { case SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5: case SSL_CK_RC2_128_CBC_WITH_MD5: case SSL_CK_RC4_128_EXPORT40_WITH_MD5: case SSL_CK_RC4_128_WITH_MD5: case SSL_CK_DES_64_CBC_WITH_MD5: case SSL_CK_DES_192_EDE3_CBC_WITH_MD5: sec->hash = HASH_GetHashObject(HASH_AlgMD5); SECITEM_CopyItem(0, &sec->sendSecret, writeKey); SECITEM_CopyItem(0, &sec->rcvSecret, readKey); break; default: PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP); return SECFailure; } sec->hashcx = (*sec->hash->create)(); if (sec->hashcx == NULL) return SECFailure; return SECSuccess; } /************************************************************************ * All the Send functions below must acquire and release the socket's * xmitBufLock. */ /* Called from all the Send* functions below. */ static SECStatus ssl2_GetSendBuffer(sslSocket *ss, unsigned int len) { SECStatus rv = SECSuccess; PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss)); if (len < 128) { len = 128; } if (len > ss->sec.ci.sendBuf.space) { rv = sslBuffer_Grow(&ss->sec.ci.sendBuf, len); if (rv != SECSuccess) { SSL_DBG(("%d: SSL[%d]: ssl2_GetSendBuffer failed, tried to get %d bytes", SSL_GETPID(), ss->fd, len)); rv = SECFailure; } } return rv; } /* Called from: * ssl2_ClientSetupSessionCypher() <- ssl2_HandleServerHelloMessage() * ssl2_HandleRequestCertificate() <- ssl2_HandleMessage() <- ssl_Do1stHandshake() * ssl2_HandleMessage() <- ssl_Do1stHandshake() * ssl2_HandleServerHelloMessage() <- ssl_Do1stHandshake() after ssl2_BeginClientHandshake() * ssl2_HandleClientHelloMessage() <- ssl_Do1stHandshake() after ssl2_BeginServerHandshake() * * Acquires and releases the socket's xmitBufLock. */ int ssl2_SendErrorMessage(sslSocket *ss, int error) { int rv; PRUint8 msg[SSL_HL_ERROR_HBYTES]; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); msg[0] = SSL_MT_ERROR; msg[1] = MSB(error); msg[2] = LSB(error); ssl_GetXmitBufLock(ss); /***************************************/ SSL_TRC(3, ("%d: SSL[%d]: sending error %d", SSL_GETPID(), ss->fd, error)); ss->handshakeBegun = 1; rv = (*ss->sec.send)(ss, msg, sizeof(msg), 0); if (rv >= 0) { rv = SECSuccess; } ssl_ReleaseXmitBufLock(ss); /***************************************/ return rv; } /* Called from ssl2_TryToFinish(). * Acquires and releases the socket's xmitBufLock. */ static SECStatus ssl2_SendClientFinishedMessage(sslSocket *ss) { SECStatus rv = SECSuccess; int sent; PRUint8 msg[1 + SSL_CONNECTIONID_BYTES]; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); ssl_GetXmitBufLock(ss); /***************************************/ if (ss->sec.ci.sentFinished == 0) { ss->sec.ci.sentFinished = 1; SSL_TRC(3, ("%d: SSL[%d]: sending client-finished", SSL_GETPID(), ss->fd)); msg[0] = SSL_MT_CLIENT_FINISHED; PORT_Memcpy(msg+1, ss->sec.ci.connectionID, sizeof(ss->sec.ci.connectionID)); DUMP_MSG(29, (ss, msg, 1 + sizeof(ss->sec.ci.connectionID))); sent = (*ss->sec.send)(ss, msg, 1 + sizeof(ss->sec.ci.connectionID), 0); rv = (sent >= 0) ? SECSuccess : (SECStatus)sent; } ssl_ReleaseXmitBufLock(ss); /***************************************/ return rv; } /* Called from * ssl2_HandleClientSessionKeyMessage() <- ssl2_HandleClientHelloMessage() * ssl2_HandleClientHelloMessage() <- ssl_Do1stHandshake() after ssl2_BeginServerHandshake() * Acquires and releases the socket's xmitBufLock. */ static SECStatus ssl2_SendServerVerifyMessage(sslSocket *ss) { PRUint8 * msg; int sendLen; int sent; SECStatus rv; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); ssl_GetXmitBufLock(ss); /***************************************/ sendLen = 1 + SSL_CHALLENGE_BYTES; rv = ssl2_GetSendBuffer(ss, sendLen); if (rv != SECSuccess) { goto done; } msg = ss->sec.ci.sendBuf.buf; msg[0] = SSL_MT_SERVER_VERIFY; PORT_Memcpy(msg+1, ss->sec.ci.clientChallenge, SSL_CHALLENGE_BYTES); DUMP_MSG(29, (ss, msg, sendLen)); sent = (*ss->sec.send)(ss, msg, sendLen, 0); rv = (sent >= 0) ? SECSuccess : (SECStatus)sent; done: ssl_ReleaseXmitBufLock(ss); /***************************************/ return rv; } /* Called from ssl2_TryToFinish(). * Acquires and releases the socket's xmitBufLock. */ static SECStatus ssl2_SendServerFinishedMessage(sslSocket *ss) { sslSessionID * sid; PRUint8 * msg; int sendLen, sent; SECStatus rv = SECSuccess; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); ssl_GetXmitBufLock(ss); /***************************************/ if (ss->sec.ci.sentFinished == 0) { ss->sec.ci.sentFinished = 1; PORT_Assert(ss->sec.ci.sid != 0); sid = ss->sec.ci.sid; SSL_TRC(3, ("%d: SSL[%d]: sending server-finished", SSL_GETPID(), ss->fd)); sendLen = 1 + sizeof(sid->u.ssl2.sessionID); rv = ssl2_GetSendBuffer(ss, sendLen); if (rv != SECSuccess) { goto done; } msg = ss->sec.ci.sendBuf.buf; msg[0] = SSL_MT_SERVER_FINISHED; PORT_Memcpy(msg+1, sid->u.ssl2.sessionID, sizeof(sid->u.ssl2.sessionID)); DUMP_MSG(29, (ss, msg, sendLen)); sent = (*ss->sec.send)(ss, msg, sendLen, 0); if (sent < 0) { /* If send failed, it is now a bogus session-id */ if (ss->sec.uncache) (*ss->sec.uncache)(sid); rv = (SECStatus)sent; } else if (!ss->opt.noCache) { /* Put the sid in session-id cache, (may already be there) */ (*ss->sec.cache)(sid); rv = SECSuccess; } ssl_FreeSID(sid); ss->sec.ci.sid = 0; } done: ssl_ReleaseXmitBufLock(ss); /***************************************/ return rv; } /* Called from ssl2_ClientSetupSessionCypher() <- * ssl2_HandleServerHelloMessage() * after ssl2_BeginClientHandshake() * Acquires and releases the socket's xmitBufLock. */ static SECStatus ssl2_SendSessionKeyMessage(sslSocket *ss, int cipher, int keySize, PRUint8 *ca, int caLen, PRUint8 *ck, int ckLen, PRUint8 *ek, int ekLen) { PRUint8 * msg; int sendLen; int sent; SECStatus rv; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); ssl_GetXmitBufLock(ss); /***************************************/ sendLen = SSL_HL_CLIENT_MASTER_KEY_HBYTES + ckLen + ekLen + caLen; rv = ssl2_GetSendBuffer(ss, sendLen); if (rv != SECSuccess) goto done; SSL_TRC(3, ("%d: SSL[%d]: sending client-session-key", SSL_GETPID(), ss->fd)); msg = ss->sec.ci.sendBuf.buf; msg[0] = SSL_MT_CLIENT_MASTER_KEY; msg[1] = cipher; msg[2] = MSB(keySize); msg[3] = LSB(keySize); msg[4] = MSB(ckLen); msg[5] = LSB(ckLen); msg[6] = MSB(ekLen); msg[7] = LSB(ekLen); msg[8] = MSB(caLen); msg[9] = LSB(caLen); PORT_Memcpy(msg+SSL_HL_CLIENT_MASTER_KEY_HBYTES, ck, ckLen); PORT_Memcpy(msg+SSL_HL_CLIENT_MASTER_KEY_HBYTES+ckLen, ek, ekLen); PORT_Memcpy(msg+SSL_HL_CLIENT_MASTER_KEY_HBYTES+ckLen+ekLen, ca, caLen); DUMP_MSG(29, (ss, msg, sendLen)); sent = (*ss->sec.send)(ss, msg, sendLen, 0); rv = (sent >= 0) ? SECSuccess : (SECStatus)sent; done: ssl_ReleaseXmitBufLock(ss); /***************************************/ return rv; } /* Called from ssl2_TriggerNextMessage() <- ssl2_HandleMessage() * Acquires and releases the socket's xmitBufLock. */ static SECStatus ssl2_SendCertificateRequestMessage(sslSocket *ss) { PRUint8 * msg; int sent; int sendLen; SECStatus rv; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); ssl_GetXmitBufLock(ss); /***************************************/ sendLen = SSL_HL_REQUEST_CERTIFICATE_HBYTES + SSL_CHALLENGE_BYTES; rv = ssl2_GetSendBuffer(ss, sendLen); if (rv != SECSuccess) goto done; SSL_TRC(3, ("%d: SSL[%d]: sending certificate request", SSL_GETPID(), ss->fd)); /* Generate random challenge for client to encrypt */ PK11_GenerateRandom(ss->sec.ci.serverChallenge, SSL_CHALLENGE_BYTES); msg = ss->sec.ci.sendBuf.buf; msg[0] = SSL_MT_REQUEST_CERTIFICATE; msg[1] = SSL_AT_MD5_WITH_RSA_ENCRYPTION; PORT_Memcpy(msg + SSL_HL_REQUEST_CERTIFICATE_HBYTES, ss->sec.ci.serverChallenge, SSL_CHALLENGE_BYTES); DUMP_MSG(29, (ss, msg, sendLen)); sent = (*ss->sec.send)(ss, msg, sendLen, 0); rv = (sent >= 0) ? SECSuccess : (SECStatus)sent; done: ssl_ReleaseXmitBufLock(ss); /***************************************/ return rv; } /* Called from ssl2_HandleRequestCertificate() <- ssl2_HandleMessage() * Acquires and releases the socket's xmitBufLock. */ static int ssl2_SendCertificateResponseMessage(sslSocket *ss, SECItem *cert, SECItem *encCode) { PRUint8 *msg; int rv, sendLen; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); ssl_GetXmitBufLock(ss); /***************************************/ sendLen = SSL_HL_CLIENT_CERTIFICATE_HBYTES + encCode->len + cert->len; rv = ssl2_GetSendBuffer(ss, sendLen); if (rv) goto done; SSL_TRC(3, ("%d: SSL[%d]: sending certificate response", SSL_GETPID(), ss->fd)); msg = ss->sec.ci.sendBuf.buf; msg[0] = SSL_MT_CLIENT_CERTIFICATE; msg[1] = SSL_CT_X509_CERTIFICATE; msg[2] = MSB(cert->len); msg[3] = LSB(cert->len); msg[4] = MSB(encCode->len); msg[5] = LSB(encCode->len); PORT_Memcpy(msg + SSL_HL_CLIENT_CERTIFICATE_HBYTES, cert->data, cert->len); PORT_Memcpy(msg + SSL_HL_CLIENT_CERTIFICATE_HBYTES + cert->len, encCode->data, encCode->len); DUMP_MSG(29, (ss, msg, sendLen)); rv = (*ss->sec.send)(ss, msg, sendLen, 0); if (rv >= 0) { rv = SECSuccess; } done: ssl_ReleaseXmitBufLock(ss); /***************************************/ return rv; } /******************************************************************** ** Send functions above this line must aquire & release the socket's ** xmitBufLock. ** All the ssl2_Send functions below this line are called vis ss->sec.send ** and require that the caller hold the xmitBufLock. */ /* ** Called from ssl2_SendStream, ssl2_SendBlock, but not from ssl2_SendClear. */ static SECStatus ssl2_CalcMAC(PRUint8 * result, sslSecurityInfo * sec, const PRUint8 * data, unsigned int dataLen, unsigned int paddingLen) { const PRUint8 * secret = sec->sendSecret.data; unsigned int secretLen = sec->sendSecret.len; unsigned long sequenceNumber = sec->sendSequence; unsigned int nout; PRUint8 seq[4]; PRUint8 padding[32];/* XXX max blocksize? */ if (!sec->hash || !sec->hash->length) return SECSuccess; if (!sec->hashcx) return SECFailure; /* Reset hash function */ (*sec->hash->begin)(sec->hashcx); /* Feed hash the data */ (*sec->hash->update)(sec->hashcx, secret, secretLen); (*sec->hash->update)(sec->hashcx, data, dataLen); PORT_Memset(padding, paddingLen, paddingLen); (*sec->hash->update)(sec->hashcx, padding, paddingLen); seq[0] = (PRUint8) (sequenceNumber >> 24); seq[1] = (PRUint8) (sequenceNumber >> 16); seq[2] = (PRUint8) (sequenceNumber >> 8); seq[3] = (PRUint8) (sequenceNumber); PRINT_BUF(60, (0, "calc-mac secret:", secret, secretLen)); PRINT_BUF(60, (0, "calc-mac data:", data, dataLen)); PRINT_BUF(60, (0, "calc-mac padding:", padding, paddingLen)); PRINT_BUF(60, (0, "calc-mac seq:", seq, 4)); (*sec->hash->update)(sec->hashcx, seq, 4); /* Get result */ (*sec->hash->end)(sec->hashcx, result, &nout, sec->hash->length); return SECSuccess; } /* ** Maximum transmission amounts. These are tiny bit smaller than they ** need to be (they account for the MAC length plus some padding), ** assuming the MAC is 16 bytes long and the padding is a max of 7 bytes ** long. This gives an additional 9 bytes of slop to work within. */ #define MAX_STREAM_CYPHER_LEN 0x7fe0 #define MAX_BLOCK_CYPHER_LEN 0x3fe0 /* ** Send some data in the clear. ** Package up data with the length header and send it. ** ** Return count of bytes successfully written, or negative number (failure). */ static PRInt32 ssl2_SendClear(sslSocket *ss, const PRUint8 *in, PRInt32 len, PRInt32 flags) { PRUint8 * out; int rv; int amount; int count = 0; PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) ); SSL_TRC(10, ("%d: SSL[%d]: sending %d bytes in the clear", SSL_GETPID(), ss->fd, len)); PRINT_BUF(50, (ss, "clear data:", (PRUint8*) in, len)); while (len) { amount = PR_MIN( len, MAX_STREAM_CYPHER_LEN ); if (amount + 2 > ss->sec.writeBuf.space) { rv = sslBuffer_Grow(&ss->sec.writeBuf, amount + 2); if (rv != SECSuccess) { count = rv; break; } } out = ss->sec.writeBuf.buf; /* ** Construct message. */ out[0] = 0x80 | MSB(amount); out[1] = LSB(amount); PORT_Memcpy(&out[2], in, amount); /* Now send the data */ rv = ssl_DefSend(ss, out, amount + 2, flags & ~ssl_SEND_FLAG_MASK); if (rv < 0) { if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) { rv = 0; } else { /* Return short write if some data already went out... */ if (count == 0) count = rv; break; } } if ((unsigned)rv < (amount + 2)) { /* Short write. Save the data and return. */ if (ssl_SaveWriteData(ss, out + rv, amount + 2 - rv) == SECFailure) { count = SECFailure; } else { count += amount; ss->sec.sendSequence++; } break; } ss->sec.sendSequence++; in += amount; count += amount; len -= amount; } return count; } /* ** Send some data, when using a stream cipher. Stream ciphers have a ** block size of 1. Package up the data with the length header ** and send it. */ static PRInt32 ssl2_SendStream(sslSocket *ss, const PRUint8 *in, PRInt32 len, PRInt32 flags) { PRUint8 * out; int rv; int count = 0; int amount; PRUint8 macLen; int nout; int buflen; PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) ); SSL_TRC(10, ("%d: SSL[%d]: sending %d bytes using stream cipher", SSL_GETPID(), ss->fd, len)); PRINT_BUF(50, (ss, "clear data:", (PRUint8*) in, len)); while (len) { ssl_GetSpecReadLock(ss); /*************************************/ macLen = ss->sec.hash->length; amount = PR_MIN( len, MAX_STREAM_CYPHER_LEN ); buflen = amount + 2 + macLen; if (buflen > ss->sec.writeBuf.space) { rv = sslBuffer_Grow(&ss->sec.writeBuf, buflen); if (rv != SECSuccess) { goto loser; } } out = ss->sec.writeBuf.buf; nout = amount + macLen; out[0] = 0x80 | MSB(nout); out[1] = LSB(nout); /* Calculate MAC */ rv = ssl2_CalcMAC(out+2, /* put MAC here */ &ss->sec, in, amount, /* input addr & length */ 0); /* no padding */ if (rv != SECSuccess) goto loser; /* Encrypt MAC */ rv = (*ss->sec.enc)(ss->sec.writecx, out+2, &nout, macLen, out+2, macLen); if (rv) goto loser; /* Encrypt data from caller */ rv = (*ss->sec.enc)(ss->sec.writecx, out+2+macLen, &nout, amount, in, amount); if (rv) goto loser; ssl_ReleaseSpecReadLock(ss); /*************************************/ PRINT_BUF(50, (ss, "encrypted data:", out, buflen)); rv = ssl_DefSend(ss, out, buflen, flags & ~ssl_SEND_FLAG_MASK); if (rv < 0) { if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) { SSL_TRC(50, ("%d: SSL[%d]: send stream would block, " "saving data", SSL_GETPID(), ss->fd)); rv = 0; } else { SSL_TRC(10, ("%d: SSL[%d]: send stream error %d", SSL_GETPID(), ss->fd, PORT_GetError())); /* Return short write if some data already went out... */ if (count == 0) count = rv; goto done; } } if ((unsigned)rv < buflen) { /* Short write. Save the data and return. */ if (ssl_SaveWriteData(ss, out + rv, buflen - rv) == SECFailure) { count = SECFailure; } else { count += amount; ss->sec.sendSequence++; } goto done; } ss->sec.sendSequence++; in += amount; count += amount; len -= amount; } done: return count; loser: ssl_ReleaseSpecReadLock(ss); return SECFailure; } /* ** Send some data, when using a block cipher. Package up the data with ** the length header and send it. */ /* XXX assumes blocksize is > 7 */ static PRInt32 ssl2_SendBlock(sslSocket *ss, const PRUint8 *in, PRInt32 len, PRInt32 flags) { PRUint8 * out; /* begining of output buffer. */ PRUint8 * op; /* next output byte goes here. */ int rv; /* value from funcs we called. */ int count = 0; /* this function's return value. */ unsigned int hlen; /* output record hdr len, 2 or 3 */ unsigned int macLen; /* MAC is this many bytes long. */ int amount; /* of plaintext to go in record. */ unsigned int padding; /* add this many padding byte. */ int nout; /* ciphertext size after header. */ int buflen; /* size of generated record. */ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) ); SSL_TRC(10, ("%d: SSL[%d]: sending %d bytes using block cipher", SSL_GETPID(), ss->fd, len)); PRINT_BUF(50, (ss, "clear data:", in, len)); while (len) { ssl_GetSpecReadLock(ss); /*************************************/ macLen = ss->sec.hash->length; /* Figure out how much to send, including mac and padding */ amount = PR_MIN( len, MAX_BLOCK_CYPHER_LEN ); nout = amount + macLen; padding = nout & (ss->sec.blockSize - 1); if (padding) { hlen = 3; padding = ss->sec.blockSize - padding; nout += padding; } else { hlen = 2; } buflen = hlen + nout; if (buflen > ss->sec.writeBuf.space) { rv = sslBuffer_Grow(&ss->sec.writeBuf, buflen); if (rv != SECSuccess) { goto loser; } } out = ss->sec.writeBuf.buf; /* Construct header */ op = out; if (padding) { *op++ = MSB(nout); *op++ = LSB(nout); *op++ = padding; } else { *op++ = 0x80 | MSB(nout); *op++ = LSB(nout); } /* Calculate MAC */ rv = ssl2_CalcMAC(op, /* MAC goes here. */ &ss->sec, in, amount, /* intput addr, len */ padding); if (rv != SECSuccess) goto loser; op += macLen; /* Copy in the input data */ /* XXX could eliminate the copy by folding it into the encryption */ PORT_Memcpy(op, in, amount); op += amount; if (padding) { PORT_Memset(op, padding, padding); op += padding; } /* Encrypt result */ rv = (*ss->sec.enc)(ss->sec.writecx, out+hlen, &nout, buflen-hlen, out+hlen, op - (out + hlen)); if (rv) goto loser; ssl_ReleaseSpecReadLock(ss); /*************************************/ PRINT_BUF(50, (ss, "final xmit data:", out, op - out)); rv = ssl_DefSend(ss, out, op - out, flags & ~ssl_SEND_FLAG_MASK); if (rv < 0) { if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) { rv = 0; } else { SSL_TRC(10, ("%d: SSL[%d]: send block error %d", SSL_GETPID(), ss->fd, PORT_GetError())); /* Return short write if some data already went out... */ if (count == 0) count = rv; goto done; } } if (rv < (op - out)) { /* Short write. Save the data and return. */ if (ssl_SaveWriteData(ss, out + rv, op - out - rv) == SECFailure) { count = SECFailure; } else { count += amount; ss->sec.sendSequence++; } goto done; } ss->sec.sendSequence++; in += amount; count += amount; len -= amount; } done: return count; loser: ssl_ReleaseSpecReadLock(ss); return SECFailure; } /* ** Called from: ssl2_HandleServerHelloMessage, ** ssl2_HandleClientSessionKeyMessage, ** ssl2_HandleClientHelloMessage, ** */ static void ssl2_UseEncryptedSendFunc(sslSocket *ss) { ssl_GetXmitBufLock(ss); PORT_Assert(ss->sec.hashcx != 0); ss->gs.encrypted = 1; ss->sec.send = (ss->sec.blockSize > 1) ? ssl2_SendBlock : ssl2_SendStream; ssl_ReleaseXmitBufLock(ss); } /* Called while initializing socket in ssl_CreateSecurityInfo(). ** This function allows us to keep the name of ssl2_SendClear static. */ void ssl2_UseClearSendFunc(sslSocket *ss) { ss->sec.send = ssl2_SendClear; } /************************************************************************ ** END of Send functions. * *************************************************************************/ /*********************************************************************** * For SSL3, this gathers in and handles records/messages until either * the handshake is complete or application data is available. * * For SSL2, this gathers in only the next SSLV2 record. * * Called from ssl_Do1stHandshake() via function pointer ss->handshake. * Caller must hold handshake lock. * This function acquires and releases the RecvBufLock. * * returns SECSuccess for success. * returns SECWouldBlock when that value is returned by ssl2_GatherRecord() or * ssl3_GatherCompleteHandshake(). * returns SECFailure on all other errors. * * The gather functions called by ssl_GatherRecord1stHandshake are expected * to return values interpreted as follows: * 1 : the function completed without error. * 0 : the function read EOF. * -1 : read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error. * -2 : the function wants ssl_GatherRecord1stHandshake to be called again * immediately, by ssl_Do1stHandshake. * * This code is similar to, and easily confused with, DoRecv() in sslsecur.c * * This function is called from ssl_Do1stHandshake(). * The following functions put ssl_GatherRecord1stHandshake into ss->handshake: * ssl2_HandleMessage * ssl2_HandleVerifyMessage * ssl2_HandleServerHelloMessage * ssl2_BeginClientHandshake * ssl2_HandleClientSessionKeyMessage * ssl3_RestartHandshakeAfterCertReq * ssl3_RestartHandshakeAfterServerCert * ssl2_HandleClientHelloMessage * ssl2_BeginServerHandshake */ SECStatus ssl_GatherRecord1stHandshake(sslSocket *ss) { int rv; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); ssl_GetRecvBufLock(ss); /* The special case DTLS logic is needed here because the SSL/TLS * version wants to auto-detect SSL2 vs. SSL3 on the initial handshake * (ss->version == 0) but with DTLS it gets confused, so we force the * SSL3 version. */ if ((ss->version >= SSL_LIBRARY_VERSION_3_0) || IS_DTLS(ss)) { /* Wait for handshake to complete, or application data to arrive. */ rv = ssl3_GatherCompleteHandshake(ss, 0); } else { /* See if we have a complete record */ rv = ssl2_GatherRecord(ss, 0); } SSL_TRC(10, ("%d: SSL[%d]: handshake gathering, rv=%d", SSL_GETPID(), ss->fd, rv)); ssl_ReleaseRecvBufLock(ss); if (rv <= 0) { if (rv == SECWouldBlock) { /* Progress is blocked waiting for callback completion. */ SSL_TRC(10, ("%d: SSL[%d]: handshake blocked (need %d)", SSL_GETPID(), ss->fd, ss->gs.remainder)); return SECWouldBlock; } if (rv == 0) { /* EOF. Loser */ PORT_SetError(PR_END_OF_FILE_ERROR); } return SECFailure; /* rv is < 0 here. */ } SSL_TRC(10, ("%d: SSL[%d]: got handshake record of %d bytes", SSL_GETPID(), ss->fd, ss->gs.recordLen)); ss->handshake = 0; /* makes ssl_Do1stHandshake call ss->nextHandshake.*/ return SECSuccess; } /************************************************************************/ /* Called from ssl2_ServerSetupSessionCypher() * ssl2_ClientSetupSessionCypher() */ static SECStatus ssl2_FillInSID(sslSessionID * sid, int cipher, PRUint8 *keyData, int keyLen, PRUint8 *ca, int caLen, int keyBits, int secretKeyBits, SSLSignType authAlgorithm, PRUint32 authKeyBits, SSLKEAType keaType, PRUint32 keaKeyBits) { PORT_Assert(sid->references == 1); PORT_Assert(sid->cached == never_cached); PORT_Assert(sid->u.ssl2.masterKey.data == 0); PORT_Assert(sid->u.ssl2.cipherArg.data == 0); sid->version = SSL_LIBRARY_VERSION_2; sid->u.ssl2.cipherType = cipher; sid->u.ssl2.masterKey.data = (PRUint8*) PORT_Alloc(keyLen); if (!sid->u.ssl2.masterKey.data) { return SECFailure; } PORT_Memcpy(sid->u.ssl2.masterKey.data, keyData, keyLen); sid->u.ssl2.masterKey.len = keyLen; sid->u.ssl2.keyBits = keyBits; sid->u.ssl2.secretKeyBits = secretKeyBits; sid->authAlgorithm = authAlgorithm; sid->authKeyBits = authKeyBits; sid->keaType = keaType; sid->keaKeyBits = keaKeyBits; sid->lastAccessTime = sid->creationTime = ssl_Time(); sid->expirationTime = sid->creationTime + ssl_sid_timeout; if (caLen) { sid->u.ssl2.cipherArg.data = (PRUint8*) PORT_Alloc(caLen); if (!sid->u.ssl2.cipherArg.data) { return SECFailure; } sid->u.ssl2.cipherArg.len = caLen; PORT_Memcpy(sid->u.ssl2.cipherArg.data, ca, caLen); } return SECSuccess; } /* ** Construct session keys given the masterKey (tied to the session-id), ** the client's challenge and the server's nonce. ** ** Called from ssl2_CreateSessionCypher() <- */ static SECStatus ssl2_ProduceKeys(sslSocket * ss, SECItem * readKey, SECItem * writeKey, SECItem * masterKey, PRUint8 * challenge, PRUint8 * nonce, int cipherType) { PK11Context * cx = 0; unsigned nkm = 0; /* number of hashes to generate key mat. */ unsigned nkd = 0; /* size of readKey and writeKey. */ unsigned part; unsigned i; unsigned off; SECStatus rv; PRUint8 countChar; PRUint8 km[3*16]; /* buffer for key material. */ readKey->data = 0; writeKey->data = 0; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); rv = SECSuccess; cx = PK11_CreateDigestContext(SEC_OID_MD5); if (cx == NULL) { ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE); return SECFailure; } nkm = ssl_Specs[cipherType].nkm; nkd = ssl_Specs[cipherType].nkd; readKey->data = (PRUint8*) PORT_Alloc(nkd); if (!readKey->data) goto loser; readKey->len = nkd; writeKey->data = (PRUint8*) PORT_Alloc(nkd); if (!writeKey->data) goto loser; writeKey->len = nkd; /* Produce key material */ countChar = '0'; for (i = 0, off = 0; i < nkm; i++, off += 16) { rv = PK11_DigestBegin(cx); rv |= PK11_DigestOp(cx, masterKey->data, masterKey->len); rv |= PK11_DigestOp(cx, &countChar, 1); rv |= PK11_DigestOp(cx, challenge, SSL_CHALLENGE_BYTES); rv |= PK11_DigestOp(cx, nonce, SSL_CONNECTIONID_BYTES); rv |= PK11_DigestFinal(cx, km+off, &part, MD5_LENGTH); if (rv != SECSuccess) { ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE); rv = SECFailure; goto loser; } countChar++; } /* Produce keys */ PORT_Memcpy(readKey->data, km, nkd); PORT_Memcpy(writeKey->data, km + nkd, nkd); loser: PK11_DestroyContext(cx, PR_TRUE); return rv; } /* Called from ssl2_ServerSetupSessionCypher() ** <- ssl2_HandleClientSessionKeyMessage() ** <- ssl2_HandleClientHelloMessage() ** and from ssl2_ClientSetupSessionCypher() ** <- ssl2_HandleServerHelloMessage() */ static SECStatus ssl2_CreateSessionCypher(sslSocket *ss, sslSessionID *sid, PRBool isClient) { SECItem * rk = NULL; SECItem * wk = NULL; SECItem * param; SECStatus rv; int cipherType = sid->u.ssl2.cipherType; PK11SlotInfo * slot = NULL; CK_MECHANISM_TYPE mechanism; SECItem readKey; SECItem writeKey; void *readcx = 0; void *writecx = 0; readKey.data = 0; writeKey.data = 0; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); if (ss->sec.ci.sid == 0) goto sec_loser; /* don't crash if asserts are off */ /* Trying to cut down on all these switch statements that should be tables. * So, test cipherType once, here, and then use tables below. */ switch (cipherType) { case SSL_CK_RC4_128_EXPORT40_WITH_MD5: case SSL_CK_RC4_128_WITH_MD5: case SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5: case SSL_CK_RC2_128_CBC_WITH_MD5: case SSL_CK_DES_64_CBC_WITH_MD5: case SSL_CK_DES_192_EDE3_CBC_WITH_MD5: break; default: SSL_DBG(("%d: SSL[%d]: ssl2_CreateSessionCypher: unknown cipher=%d", SSL_GETPID(), ss->fd, cipherType)); PORT_SetError(isClient ? SSL_ERROR_BAD_SERVER : SSL_ERROR_BAD_CLIENT); goto sec_loser; } rk = isClient ? &readKey : &writeKey; wk = isClient ? &writeKey : &readKey; /* Produce the keys for this session */ rv = ssl2_ProduceKeys(ss, &readKey, &writeKey, &sid->u.ssl2.masterKey, ss->sec.ci.clientChallenge, ss->sec.ci.connectionID, cipherType); if (rv != SECSuccess) goto loser; PRINT_BUF(7, (ss, "Session read-key: ", rk->data, rk->len)); PRINT_BUF(7, (ss, "Session write-key: ", wk->data, wk->len)); PORT_Memcpy(ss->sec.ci.readKey, readKey.data, readKey.len); PORT_Memcpy(ss->sec.ci.writeKey, writeKey.data, writeKey.len); ss->sec.ci.keySize = readKey.len; /* Setup the MAC */ rv = ssl2_CreateMAC(&ss->sec, rk, wk, cipherType); if (rv != SECSuccess) goto loser; /* First create the session key object */ SSL_TRC(3, ("%d: SSL[%d]: using %s", SSL_GETPID(), ss->fd, ssl_cipherName[cipherType])); mechanism = ssl_Specs[cipherType].mechanism; /* set destructer before we call loser... */ ss->sec.destroy = (void (*)(void*, PRBool)) PK11_DestroyContext; slot = PK11_GetBestSlot(mechanism, ss->pkcs11PinArg); if (slot == NULL) goto loser; param = PK11_ParamFromIV(mechanism, &sid->u.ssl2.cipherArg); if (param == NULL) goto loser; readcx = PK11_CreateContextByRawKey(slot, mechanism, PK11_OriginUnwrap, CKA_DECRYPT, rk, param, ss->pkcs11PinArg); SECITEM_FreeItem(param, PR_TRUE); if (readcx == NULL) goto loser; /* build the client context */ param = PK11_ParamFromIV(mechanism, &sid->u.ssl2.cipherArg); if (param == NULL) goto loser; writecx = PK11_CreateContextByRawKey(slot, mechanism, PK11_OriginUnwrap, CKA_ENCRYPT, wk, param, ss->pkcs11PinArg); SECITEM_FreeItem(param,PR_TRUE); if (writecx == NULL) goto loser; PK11_FreeSlot(slot); rv = SECSuccess; ss->sec.enc = (SSLCipher) PK11_CipherOp; ss->sec.dec = (SSLCipher) PK11_CipherOp; ss->sec.readcx = (void *) readcx; ss->sec.writecx = (void *) writecx; ss->sec.blockSize = ssl_Specs[cipherType].blockSize; ss->sec.blockShift = ssl_Specs[cipherType].blockShift; ss->sec.cipherType = sid->u.ssl2.cipherType; ss->sec.keyBits = sid->u.ssl2.keyBits; ss->sec.secretKeyBits = sid->u.ssl2.secretKeyBits; goto done; loser: if (ss->sec.destroy) { if (readcx) (*ss->sec.destroy)(readcx, PR_TRUE); if (writecx) (*ss->sec.destroy)(writecx, PR_TRUE); } ss->sec.destroy = NULL; if (slot) PK11_FreeSlot(slot); sec_loser: rv = SECFailure; done: if (rk) { SECITEM_ZfreeItem(rk, PR_FALSE); } if (wk) { SECITEM_ZfreeItem(wk, PR_FALSE); } return rv; } /* ** Setup the server ciphers given information from a CLIENT-MASTER-KEY ** message. ** "ss" pointer to the ssl-socket object ** "cipher" the cipher type to use ** "keyBits" the size of the final cipher key ** "ck" the clear-key data ** "ckLen" the number of bytes of clear-key data ** "ek" the encrypted-key data ** "ekLen" the number of bytes of encrypted-key data ** "ca" the cipher-arg data ** "caLen" the number of bytes of cipher-arg data ** ** The MASTER-KEY is constructed by first decrypting the encrypted-key ** data. This produces the SECRET-KEY-DATA. The MASTER-KEY is composed by ** concatenating the clear-key data with the SECRET-KEY-DATA. This code ** checks to make sure that the client didn't send us an improper amount ** of SECRET-KEY-DATA (it restricts the length of that data to match the ** spec). ** ** Called from ssl2_HandleClientSessionKeyMessage(). */ static SECStatus ssl2_ServerSetupSessionCypher(sslSocket *ss, int cipher, unsigned int keyBits, PRUint8 *ck, unsigned int ckLen, PRUint8 *ek, unsigned int ekLen, PRUint8 *ca, unsigned int caLen) { PRUint8 * dk = NULL; /* decrypted master key */ sslSessionID * sid; sslServerCerts * sc = ss->serverCerts + kt_rsa; PRUint8 * kbuf = 0; /* buffer for RSA decrypted data. */ unsigned int ddLen; /* length of RSA decrypted data in kbuf */ unsigned int keySize; unsigned int dkLen; /* decrypted key length in bytes */ int modulusLen; SECStatus rv; PRUint16 allowed; /* cipher kinds enabled and allowed by policy */ PRUint8 mkbuf[SSL_MAX_MASTER_KEY_BYTES]; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); PORT_Assert((sc->SERVERKEY != 0)); PORT_Assert((ss->sec.ci.sid != 0)); sid = ss->sec.ci.sid; /* Trying to cut down on all these switch statements that should be tables. * So, test cipherType once, here, and then use tables below. */ switch (cipher) { case SSL_CK_RC4_128_EXPORT40_WITH_MD5: case SSL_CK_RC4_128_WITH_MD5: case SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5: case SSL_CK_RC2_128_CBC_WITH_MD5: case SSL_CK_DES_64_CBC_WITH_MD5: case SSL_CK_DES_192_EDE3_CBC_WITH_MD5: break; default: SSL_DBG(("%d: SSL[%d]: ssl2_ServerSetupSessionCypher: unknown cipher=%d", SSL_GETPID(), ss->fd, cipher)); PORT_SetError(SSL_ERROR_BAD_CLIENT); goto loser; } allowed = ss->allowedByPolicy & ss->chosenPreference & SSL_CB_IMPLEMENTED; if (!(allowed & (1 << cipher))) { /* client chose a kind we don't allow! */ SSL_DBG(("%d: SSL[%d]: disallowed cipher=%d", SSL_GETPID(), ss->fd, cipher)); PORT_SetError(SSL_ERROR_BAD_CLIENT); goto loser; } keySize = ssl_Specs[cipher].keyLen; if (keyBits != keySize * BPB) { SSL_DBG(("%d: SSL[%d]: invalid master secret key length=%d (bits)!", SSL_GETPID(), ss->fd, keyBits)); PORT_SetError(SSL_ERROR_BAD_CLIENT); goto loser; } if (ckLen != ssl_Specs[cipher].pubLen) { SSL_DBG(("%d: SSL[%d]: invalid clear key length, ckLen=%d (bytes)!", SSL_GETPID(), ss->fd, ckLen)); PORT_SetError(SSL_ERROR_BAD_CLIENT); goto loser; } if (caLen != ssl_Specs[cipher].ivLen) { SSL_DBG(("%d: SSL[%d]: invalid key args length, caLen=%d (bytes)!", SSL_GETPID(), ss->fd, caLen)); PORT_SetError(SSL_ERROR_BAD_CLIENT); goto loser; } modulusLen = PK11_GetPrivateModulusLen(sc->SERVERKEY); if (modulusLen == -1) { /* XXX If the key is bad, then PK11_PubDecryptRaw will fail below. */ modulusLen = ekLen; } if (ekLen > modulusLen || ekLen + ckLen < keySize) { SSL_DBG(("%d: SSL[%d]: invalid encrypted key length, ekLen=%d (bytes)!", SSL_GETPID(), ss->fd, ekLen)); PORT_SetError(SSL_ERROR_BAD_CLIENT); goto loser; } /* allocate the buffer to hold the decrypted portion of the key. */ kbuf = (PRUint8*)PORT_Alloc(modulusLen); if (!kbuf) { goto loser; } dkLen = keySize - ckLen; dk = kbuf + modulusLen - dkLen; /* Decrypt encrypted half of the key. ** NOTE: PK11_PubDecryptRaw will barf on a non-RSA key. This is ** desired behavior here. */ rv = PK11_PubDecryptRaw(sc->SERVERKEY, kbuf, &ddLen, modulusLen, ek, ekLen); if (rv != SECSuccess) goto hide_loser; /* Is the length of the decrypted data (ddLen) the expected value? */ if (modulusLen != ddLen) goto hide_loser; /* Cheaply verify that PKCS#1 was used to format the encryption block */ if ((kbuf[0] != 0x00) || (kbuf[1] != 0x02) || (dk[-1] != 0x00)) { SSL_DBG(("%d: SSL[%d]: strange encryption block", SSL_GETPID(), ss->fd)); PORT_SetError(SSL_ERROR_BAD_CLIENT); goto hide_loser; } /* Make sure we're not subject to a version rollback attack. */ if (!SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) { static const PRUint8 threes[8] = { 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }; if (PORT_Memcmp(dk - 8 - 1, threes, 8) == 0) { PORT_SetError(SSL_ERROR_BAD_CLIENT); goto hide_loser; } } if (0) { hide_loser: /* Defense against the Bleichenbacher attack. * Provide the client with NO CLUES that the decrypted master key * was erroneous. Don't send any error messages. * Instead, Generate a completely bogus master key . */ PK11_GenerateRandom(dk, dkLen); } /* ** Construct master key out of the pieces. */ if (ckLen) { PORT_Memcpy(mkbuf, ck, ckLen); } PORT_Memcpy(mkbuf + ckLen, dk, dkLen); /* Fill in session-id */ rv = ssl2_FillInSID(sid, cipher, mkbuf, keySize, ca, caLen, keyBits, keyBits - (ckLen<<3), ss->sec.authAlgorithm, ss->sec.authKeyBits, ss->sec.keaType, ss->sec.keaKeyBits); if (rv != SECSuccess) { goto loser; } /* Create session ciphers */ rv = ssl2_CreateSessionCypher(ss, sid, PR_FALSE); if (rv != SECSuccess) { goto loser; } SSL_TRC(1, ("%d: SSL[%d]: server, using %s cipher, clear=%d total=%d", SSL_GETPID(), ss->fd, ssl_cipherName[cipher], ckLen<<3, keySize<<3)); rv = SECSuccess; goto done; loser: rv = SECFailure; done: PORT_Free(kbuf); return rv; } /************************************************************************/ /* ** Rewrite the incoming cipher specs, comparing to list of specs we support, ** (ss->cipherSpecs) and eliminating anything we don't support ** * Note: Our list may contain SSL v3 ciphers. * We MUST NOT match on any of those. * Fortunately, this is easy to detect because SSLv3 ciphers have zero * in the first byte, and none of the SSLv2 ciphers do. * * Called from ssl2_HandleClientHelloMessage(). * Returns the number of bytes of "qualified cipher specs", * which is typically a multiple of 3, but will be zero if there are none. */ static int ssl2_QualifyCypherSpecs(sslSocket *ss, PRUint8 * cs, /* cipher specs in client hello msg. */ int csLen) { PRUint8 * ms; PRUint8 * hs; PRUint8 * qs; int mc; int hc; PRUint8 qualifiedSpecs[ssl2_NUM_SUITES_IMPLEMENTED * 3]; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); if (!ss->cipherSpecs) { SECStatus rv = ssl2_ConstructCipherSpecs(ss); if (rv != SECSuccess || !ss->cipherSpecs) return 0; } PRINT_BUF(10, (ss, "specs from client:", cs, csLen)); qs = qualifiedSpecs; ms = ss->cipherSpecs; for (mc = ss->sizeCipherSpecs; mc > 0; mc -= 3, ms += 3) { if (ms[0] == 0) continue; for (hs = cs, hc = csLen; hc > 0; hs += 3, hc -= 3) { if ((hs[0] == ms[0]) && (hs[1] == ms[1]) && (hs[2] == ms[2])) { /* Copy this cipher spec into the "keep" section */ qs[0] = hs[0]; qs[1] = hs[1]; qs[2] = hs[2]; qs += 3; break; } } } hc = qs - qualifiedSpecs; PRINT_BUF(10, (ss, "qualified specs from client:", qualifiedSpecs, hc)); PORT_Memcpy(cs, qualifiedSpecs, hc); return hc; } /* ** Pick the best cipher we can find, given the array of server cipher ** specs. Returns cipher number (e.g. SSL_CK_*), or -1 for no overlap. ** If successful, stores the master key size (bytes) in *pKeyLen. ** ** This is correct only for the client side, but presently ** this function is only called from ** ssl2_ClientSetupSessionCypher() <- ssl2_HandleServerHelloMessage() ** ** Note that most servers only return a single cipher suite in their ** ServerHello messages. So, the code below for finding the "best" cipher ** suite usually has only one choice. The client and server should send ** their cipher suite lists sorted in descending order by preference. */ static int ssl2_ChooseSessionCypher(sslSocket *ss, int hc, /* number of cs's in hs. */ PRUint8 * hs, /* server hello's cipher suites. */ int * pKeyLen) /* out: sym key size in bytes. */ { PRUint8 * ms; unsigned int i; int bestKeySize; int bestRealKeySize; int bestCypher; int keySize; int realKeySize; PRUint8 * ohs = hs; const PRUint8 * preferred; static const PRUint8 noneSuch[3] = { 0, 0, 0 }; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); if (!ss->cipherSpecs) { SECStatus rv = ssl2_ConstructCipherSpecs(ss); if (rv != SECSuccess || !ss->cipherSpecs) goto loser; } if (!ss->preferredCipher) { unsigned int allowed = ss->allowedByPolicy & ss->chosenPreference & SSL_CB_IMPLEMENTED; if (allowed) { preferred = implementedCipherSuites; for (i = ssl2_NUM_SUITES_IMPLEMENTED; i > 0; --i) { if (0 != (allowed & (1U << preferred[0]))) { ss->preferredCipher = preferred; break; } preferred += 3; } } } preferred = ss->preferredCipher ? ss->preferredCipher : noneSuch; /* ** Scan list of ciphers received from peer and look for a match in ** our list. * Note: Our list may contain SSL v3 ciphers. * We MUST NOT match on any of those. * Fortunately, this is easy to detect because SSLv3 ciphers have zero * in the first byte, and none of the SSLv2 ciphers do. */ bestKeySize = bestRealKeySize = 0; bestCypher = -1; while (--hc >= 0) { for (i = 0, ms = ss->cipherSpecs; i < ss->sizeCipherSpecs; i += 3, ms += 3) { if ((hs[0] == preferred[0]) && (hs[1] == preferred[1]) && (hs[2] == preferred[2]) && hs[0] != 0) { /* Pick this cipher immediately! */ *pKeyLen = (((hs[1] << 8) | hs[2]) + 7) >> 3; return hs[0]; } if ((hs[0] == ms[0]) && (hs[1] == ms[1]) && (hs[2] == ms[2]) && hs[0] != 0) { /* Found a match */ /* Use secret keySize to determine which cipher is best */ realKeySize = (hs[1] << 8) | hs[2]; switch (hs[0]) { case SSL_CK_RC4_128_EXPORT40_WITH_MD5: case SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5: keySize = 40; break; default: keySize = realKeySize; break; } if (keySize > bestKeySize) { bestCypher = hs[0]; bestKeySize = keySize; bestRealKeySize = realKeySize; } } } hs += 3; } if (bestCypher < 0) { /* ** No overlap between server and client. Re-examine server list ** to see what kind of ciphers it does support so that we can set ** the error code appropriately. */ if ((ohs[0] == SSL_CK_RC4_128_WITH_MD5) || (ohs[0] == SSL_CK_RC2_128_CBC_WITH_MD5)) { PORT_SetError(SSL_ERROR_US_ONLY_SERVER); } else if ((ohs[0] == SSL_CK_RC4_128_EXPORT40_WITH_MD5) || (ohs[0] == SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5)) { PORT_SetError(SSL_ERROR_EXPORT_ONLY_SERVER); } else { PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP); } SSL_DBG(("%d: SSL[%d]: no cipher overlap", SSL_GETPID(), ss->fd)); goto loser; } *pKeyLen = (bestRealKeySize + 7) >> 3; return bestCypher; loser: return -1; } static SECStatus ssl2_ClientHandleServerCert(sslSocket *ss, PRUint8 *certData, int certLen) { CERTCertificate *cert = NULL; SECItem certItem; certItem.data = certData; certItem.len = certLen; /* decode the certificate */ cert = CERT_NewTempCertificate(ss->dbHandle, &certItem, NULL, PR_FALSE, PR_TRUE); if (cert == NULL) { SSL_DBG(("%d: SSL[%d]: decode of server certificate fails", SSL_GETPID(), ss->fd)); PORT_SetError(SSL_ERROR_BAD_CERTIFICATE); return SECFailure; } #ifdef TRACE { if (ssl_trace >= 1) { char *issuer; char *subject; issuer = CERT_NameToAscii(&cert->issuer); subject = CERT_NameToAscii(&cert->subject); SSL_TRC(1,("%d: server certificate issuer: '%s'", SSL_GETPID(), issuer ? issuer : "OOPS")); SSL_TRC(1,("%d: server name: '%s'", SSL_GETPID(), subject ? subject : "OOPS")); PORT_Free(issuer); PORT_Free(subject); } } #endif ss->sec.peerCert = cert; return SECSuccess; } /* * Format one block of data for public/private key encryption using * the rules defined in PKCS #1. SSL2 does this itself to handle the * rollback detection. */ #define RSA_BLOCK_MIN_PAD_LEN 8 #define RSA_BLOCK_FIRST_OCTET 0x00 #define RSA_BLOCK_AFTER_PAD_OCTET 0x00 #define RSA_BLOCK_PUBLIC_OCTET 0x02 unsigned char * ssl_FormatSSL2Block(unsigned modulusLen, SECItem *data) { unsigned char *block; unsigned char *bp; int padLen; SECStatus rv; int i; if (modulusLen < data->len + (3 + RSA_BLOCK_MIN_PAD_LEN)) { PORT_SetError(SEC_ERROR_BAD_KEY); return NULL; } block = (unsigned char *) PORT_Alloc(modulusLen); if (block == NULL) return NULL; bp = block; /* * All RSA blocks start with two octets: * 0x00 || BlockType */ *bp++ = RSA_BLOCK_FIRST_OCTET; *bp++ = RSA_BLOCK_PUBLIC_OCTET; /* * 0x00 || BT || Pad || 0x00 || ActualData * 1 1 padLen 1 data->len * Pad is all non-zero random bytes. */ padLen = modulusLen - data->len - 3; PORT_Assert (padLen >= RSA_BLOCK_MIN_PAD_LEN); rv = PK11_GenerateRandom(bp, padLen); if (rv == SECFailure) goto loser; /* replace all the 'zero' bytes */ for (i = 0; i < padLen; i++) { while (bp[i] == RSA_BLOCK_AFTER_PAD_OCTET) { rv = PK11_GenerateRandom(bp+i, 1); if (rv == SECFailure) goto loser; } } bp += padLen; *bp++ = RSA_BLOCK_AFTER_PAD_OCTET; PORT_Memcpy (bp, data->data, data->len); return block; loser: if (block) PORT_Free(block); return NULL; } /* ** Given the server's public key and cipher specs, generate a session key ** that is ready to use for encrypting/decrypting the byte stream. At ** the same time, generate the SSL_MT_CLIENT_MASTER_KEY message and ** send it to the server. ** ** Called from ssl2_HandleServerHelloMessage() */ static SECStatus ssl2_ClientSetupSessionCypher(sslSocket *ss, PRUint8 *cs, int csLen) { sslSessionID * sid; PRUint8 * ca; /* points to iv data, or NULL if none. */ PRUint8 * ekbuf = 0; CERTCertificate * cert = 0; SECKEYPublicKey * serverKey = 0; unsigned modulusLen = 0; SECStatus rv; int cipher; int keyLen; /* cipher symkey size in bytes. */ int ckLen; /* publicly reveal this many bytes of key. */ int caLen; /* length of IV data at *ca. */ int nc; unsigned char *eblock; /* holds unencrypted PKCS#1 formatted key. */ SECItem rek; /* holds portion of symkey to be encrypted. */ PRUint8 keyData[SSL_MAX_MASTER_KEY_BYTES]; PRUint8 iv [8]; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); eblock = NULL; sid = ss->sec.ci.sid; PORT_Assert(sid != 0); cert = ss->sec.peerCert; serverKey = CERT_ExtractPublicKey(cert); if (!serverKey) { SSL_DBG(("%d: SSL[%d]: extract public key failed: error=%d", SSL_GETPID(), ss->fd, PORT_GetError())); PORT_SetError(SSL_ERROR_BAD_CERTIFICATE); rv = SECFailure; goto loser2; } ss->sec.authAlgorithm = ssl_sign_rsa; ss->sec.keaType = ssl_kea_rsa; ss->sec.keaKeyBits = \ ss->sec.authKeyBits = SECKEY_PublicKeyStrengthInBits(serverKey); /* Choose a compatible cipher with the server */ nc = csLen / 3; cipher = ssl2_ChooseSessionCypher(ss, nc, cs, &keyLen); if (cipher < 0) { /* ssl2_ChooseSessionCypher has set error code. */ ssl2_SendErrorMessage(ss, SSL_PE_NO_CYPHERS); goto loser; } /* Generate the random keys */ PK11_GenerateRandom(keyData, sizeof(keyData)); /* ** Next, carve up the keys into clear and encrypted portions. The ** clear data is taken from the start of keyData and the encrypted ** portion from the remainder. Note that each of these portions is ** carved in half, one half for the read-key and one for the ** write-key. */ ca = 0; /* We know that cipher is a legit value here, because * ssl2_ChooseSessionCypher doesn't return bogus values. */ ckLen = ssl_Specs[cipher].pubLen; /* cleartext key length. */ caLen = ssl_Specs[cipher].ivLen; /* IV length. */ if (caLen) { PORT_Assert(sizeof iv >= caLen); PK11_GenerateRandom(iv, caLen); ca = iv; } /* Fill in session-id */ rv = ssl2_FillInSID(sid, cipher, keyData, keyLen, ca, caLen, keyLen << 3, (keyLen - ckLen) << 3, ss->sec.authAlgorithm, ss->sec.authKeyBits, ss->sec.keaType, ss->sec.keaKeyBits); if (rv != SECSuccess) { goto loser; } SSL_TRC(1, ("%d: SSL[%d]: client, using %s cipher, clear=%d total=%d", SSL_GETPID(), ss->fd, ssl_cipherName[cipher], ckLen<<3, keyLen<<3)); /* Now setup read and write ciphers */ rv = ssl2_CreateSessionCypher(ss, sid, PR_TRUE); if (rv != SECSuccess) { goto loser; } /* ** Fill in the encryption buffer with some random bytes. Then ** copy in the portion of the session key we are encrypting. */ modulusLen = SECKEY_PublicKeyStrength(serverKey); rek.data = keyData + ckLen; rek.len = keyLen - ckLen; eblock = ssl_FormatSSL2Block(modulusLen, &rek); if (eblock == NULL) goto loser; /* Set up the padding for version 2 rollback detection. */ /* XXX We should really use defines here */ if (!SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) { PORT_Assert((modulusLen - rek.len) > 12); PORT_Memset(eblock + modulusLen - rek.len - 8 - 1, 0x03, 8); } ekbuf = (PRUint8*) PORT_Alloc(modulusLen); if (!ekbuf) goto loser; PRINT_BUF(10, (ss, "master key encryption block:", eblock, modulusLen)); /* Encrypt ekitem */ rv = PK11_PubEncryptRaw(serverKey, ekbuf, eblock, modulusLen, ss->pkcs11PinArg); if (rv) goto loser; /* Now we have everything ready to send */ rv = ssl2_SendSessionKeyMessage(ss, cipher, keyLen << 3, ca, caLen, keyData, ckLen, ekbuf, modulusLen); if (rv != SECSuccess) { goto loser; } rv = SECSuccess; goto done; loser: rv = SECFailure; loser2: done: PORT_Memset(keyData, 0, sizeof(keyData)); PORT_ZFree(ekbuf, modulusLen); PORT_ZFree(eblock, modulusLen); SECKEY_DestroyPublicKey(serverKey); return rv; } /************************************************************************/ /* * Called from ssl2_HandleMessage in response to SSL_MT_SERVER_FINISHED message. * Caller holds recvBufLock and handshakeLock */ static void ssl2_ClientRegSessionID(sslSocket *ss, PRUint8 *s) { sslSessionID *sid = ss->sec.ci.sid; /* Record entry in nonce cache */ if (sid->peerCert == NULL) { PORT_Memcpy(sid->u.ssl2.sessionID, s, sizeof(sid->u.ssl2.sessionID)); sid->peerCert = CERT_DupCertificate(ss->sec.peerCert); } if (!ss->opt.noCache) (*ss->sec.cache)(sid); } /* Called from ssl2_HandleMessage() */ static SECStatus ssl2_TriggerNextMessage(sslSocket *ss) { SECStatus rv; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); if ((ss->sec.ci.requiredElements & CIS_HAVE_CERTIFICATE) && !(ss->sec.ci.sentElements & CIS_HAVE_CERTIFICATE)) { ss->sec.ci.sentElements |= CIS_HAVE_CERTIFICATE; rv = ssl2_SendCertificateRequestMessage(ss); return rv; } return SECSuccess; } /* See if it's time to send our finished message, or if the handshakes are ** complete. Send finished message if appropriate. ** Returns SECSuccess unless anything goes wrong. ** ** Called from ssl2_HandleMessage, ** ssl2_HandleVerifyMessage ** ssl2_HandleServerHelloMessage ** ssl2_HandleClientSessionKeyMessage */ static SECStatus ssl2_TryToFinish(sslSocket *ss) { SECStatus rv; char e, ef; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); e = ss->sec.ci.elements; ef = e | CIS_HAVE_FINISHED; if ((ef & ss->sec.ci.requiredElements) == ss->sec.ci.requiredElements) { if (ss->sec.isServer) { /* Send server finished message if we already didn't */ rv = ssl2_SendServerFinishedMessage(ss); } else { /* Send client finished message if we already didn't */ rv = ssl2_SendClientFinishedMessage(ss); } if (rv != SECSuccess) { return rv; } if ((e & ss->sec.ci.requiredElements) == ss->sec.ci.requiredElements) { /* Totally finished */ ss->handshake = 0; return SECSuccess; } } return SECSuccess; } /* ** Called from ssl2_HandleRequestCertificate */ static SECStatus ssl2_SignResponse(sslSocket *ss, SECKEYPrivateKey *key, SECItem *response) { SGNContext * sgn = NULL; PRUint8 * challenge; unsigned int len; SECStatus rv = SECFailure; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); challenge = ss->sec.ci.serverChallenge; len = ss->sec.ci.serverChallengeLen; /* Sign the expected data... */ sgn = SGN_NewContext(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION,key); if (!sgn) goto done; rv = SGN_Begin(sgn); if (rv != SECSuccess) goto done; rv = SGN_Update(sgn, ss->sec.ci.readKey, ss->sec.ci.keySize); if (rv != SECSuccess) goto done; rv = SGN_Update(sgn, ss->sec.ci.writeKey, ss->sec.ci.keySize); if (rv != SECSuccess) goto done; rv = SGN_Update(sgn, challenge, len); if (rv != SECSuccess) goto done; rv = SGN_Update(sgn, ss->sec.peerCert->derCert.data, ss->sec.peerCert->derCert.len); if (rv != SECSuccess) goto done; rv = SGN_End(sgn, response); if (rv != SECSuccess) goto done; done: SGN_DestroyContext(sgn, PR_TRUE); return rv == SECSuccess ? SECSuccess : SECFailure; } /* ** Try to handle a request-certificate message. Get client's certificate ** and private key and sign a message for the server to see. ** Caller must hold handshakeLock ** ** Called from ssl2_HandleMessage(). */ static int ssl2_HandleRequestCertificate(sslSocket *ss) { CERTCertificate * cert = NULL; /* app-selected client cert. */ SECKEYPrivateKey *key = NULL; /* priv key for cert. */ SECStatus rv; SECItem response; int ret = 0; PRUint8 authType; /* * These things all need to be initialized before we can "goto loser". */ response.data = NULL; /* get challenge info from connectionInfo */ authType = ss->sec.ci.authType; if (authType != SSL_AT_MD5_WITH_RSA_ENCRYPTION) { SSL_TRC(7, ("%d: SSL[%d]: unsupported auth type 0x%x", SSL_GETPID(), ss->fd, authType)); goto no_cert_error; } /* Get certificate and private-key from client */ if (!ss->getClientAuthData) { SSL_TRC(7, ("%d: SSL[%d]: client doesn't support client-auth", SSL_GETPID(), ss->fd)); goto no_cert_error; } ret = (*ss->getClientAuthData)(ss->getClientAuthDataArg, ss->fd, NULL, &cert, &key); if ( ret == SECWouldBlock ) { PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2); ret = -1; goto loser; } if (ret) { goto no_cert_error; } /* check what the callback function returned */ if ((!cert) || (!key)) { /* we are missing either the key or cert */ if (cert) { /* got a cert, but no key - free it */ CERT_DestroyCertificate(cert); cert = NULL; } if (key) { /* got a key, but no cert - free it */ SECKEY_DestroyPrivateKey(key); key = NULL; } goto no_cert_error; } rv = ssl2_SignResponse(ss, key, &response); if ( rv != SECSuccess ) { ret = -1; goto loser; } /* Send response message */ ret = ssl2_SendCertificateResponseMessage(ss, &cert->derCert, &response); /* Now, remember the cert we sent. But first, forget any previous one. */ if (ss->sec.localCert) { CERT_DestroyCertificate(ss->sec.localCert); } ss->sec.localCert = CERT_DupCertificate(cert); PORT_Assert(!ss->sec.ci.sid->localCert); if (ss->sec.ci.sid->localCert) { CERT_DestroyCertificate(ss->sec.ci.sid->localCert); } ss->sec.ci.sid->localCert = cert; cert = NULL; goto done; no_cert_error: SSL_TRC(7, ("%d: SSL[%d]: no certificate (ret=%d)", SSL_GETPID(), ss->fd, ret)); ret = ssl2_SendErrorMessage(ss, SSL_PE_NO_CERTIFICATE); loser: done: if ( cert ) { CERT_DestroyCertificate(cert); } if ( key ) { SECKEY_DestroyPrivateKey(key); } if ( response.data ) { PORT_Free(response.data); } return ret; } /* ** Called from ssl2_HandleMessage for SSL_MT_CLIENT_CERTIFICATE message. ** Caller must hold HandshakeLock and RecvBufLock, since cd and response ** are contained in the gathered input data. */ static SECStatus ssl2_HandleClientCertificate(sslSocket * ss, PRUint8 certType, /* XXX unused */ PRUint8 * cd, unsigned int cdLen, PRUint8 * response, unsigned int responseLen) { CERTCertificate *cert = NULL; SECKEYPublicKey *pubKey = NULL; VFYContext * vfy = NULL; SECItem * derCert; SECStatus rv = SECFailure; SECItem certItem; SECItem rep; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); /* Extract the certificate */ certItem.data = cd; certItem.len = cdLen; cert = CERT_NewTempCertificate(ss->dbHandle, &certItem, NULL, PR_FALSE, PR_TRUE); if (cert == NULL) { goto loser; } /* save the certificate, since the auth routine will need it */ ss->sec.peerCert = cert; /* Extract the public key */ pubKey = CERT_ExtractPublicKey(cert); if (!pubKey) goto loser; /* Verify the response data... */ rep.data = response; rep.len = responseLen; /* SSL 2.0 only supports RSA certs, so we don't have to worry about * DSA here. */ vfy = VFY_CreateContext(pubKey, &rep, SEC_OID_PKCS1_RSA_ENCRYPTION, ss->pkcs11PinArg); if (!vfy) goto loser; rv = VFY_Begin(vfy); if (rv) goto loser; rv = VFY_Update(vfy, ss->sec.ci.readKey, ss->sec.ci.keySize); if (rv) goto loser; rv = VFY_Update(vfy, ss->sec.ci.writeKey, ss->sec.ci.keySize); if (rv) goto loser; rv = VFY_Update(vfy, ss->sec.ci.serverChallenge, SSL_CHALLENGE_BYTES); if (rv) goto loser; derCert = &ss->serverCerts[kt_rsa].serverCert->derCert; rv = VFY_Update(vfy, derCert->data, derCert->len); if (rv) goto loser; rv = VFY_End(vfy); if (rv) goto loser; /* Now ask the server application if it likes the certificate... */ rv = (SECStatus) (*ss->authCertificate)(ss->authCertificateArg, ss->fd, PR_TRUE, PR_TRUE); /* Hey, it liked it. */ if (SECSuccess == rv) goto done; loser: ss->sec.peerCert = NULL; CERT_DestroyCertificate(cert); done: VFY_DestroyContext(vfy, PR_TRUE); SECKEY_DestroyPublicKey(pubKey); return rv; } /* ** Handle remaining messages between client/server. Process finished ** messages from either side and any authentication requests. ** This should only be called for SSLv2 handshake messages, ** not for application data records. ** Caller must hold handshake lock. ** ** Called from ssl_Do1stHandshake(). ** */ static SECStatus ssl2_HandleMessage(sslSocket *ss) { PRUint8 * data; PRUint8 * cid; unsigned len, certType, certLen, responseLen; int rv; int rv2; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); ssl_GetRecvBufLock(ss); data = ss->gs.buf.buf + ss->gs.recordOffset; if (ss->gs.recordLen < 1) { goto bad_peer; } SSL_TRC(3, ("%d: SSL[%d]: received %d message", SSL_GETPID(), ss->fd, data[0])); DUMP_MSG(29, (ss, data, ss->gs.recordLen)); switch (data[0]) { case SSL_MT_CLIENT_FINISHED: if (ss->sec.ci.elements & CIS_HAVE_FINISHED) { SSL_DBG(("%d: SSL[%d]: dup client-finished message", SSL_GETPID(), ss->fd)); goto bad_peer; } /* See if nonce matches */ len = ss->gs.recordLen - 1; cid = data + 1; if ((len != sizeof(ss->sec.ci.connectionID)) || (PORT_Memcmp(ss->sec.ci.connectionID, cid, len) != 0)) { SSL_DBG(("%d: SSL[%d]: bad connection-id", SSL_GETPID(), ss->fd)); PRINT_BUF(5, (ss, "sent connection-id", ss->sec.ci.connectionID, sizeof(ss->sec.ci.connectionID))); PRINT_BUF(5, (ss, "rcvd connection-id", cid, len)); goto bad_peer; } SSL_TRC(5, ("%d: SSL[%d]: got client finished, waiting for 0x%d", SSL_GETPID(), ss->fd, ss->sec.ci.requiredElements ^ ss->sec.ci.elements)); ss->sec.ci.elements |= CIS_HAVE_FINISHED; break; case SSL_MT_SERVER_FINISHED: if (ss->sec.ci.elements & CIS_HAVE_FINISHED) { SSL_DBG(("%d: SSL[%d]: dup server-finished message", SSL_GETPID(), ss->fd)); goto bad_peer; } if (ss->gs.recordLen - 1 != SSL2_SESSIONID_BYTES) { SSL_DBG(("%d: SSL[%d]: bad server-finished message, len=%d", SSL_GETPID(), ss->fd, ss->gs.recordLen)); goto bad_peer; } ssl2_ClientRegSessionID(ss, data+1); SSL_TRC(5, ("%d: SSL[%d]: got server finished, waiting for 0x%d", SSL_GETPID(), ss->fd, ss->sec.ci.requiredElements ^ ss->sec.ci.elements)); ss->sec.ci.elements |= CIS_HAVE_FINISHED; break; case SSL_MT_REQUEST_CERTIFICATE: len = ss->gs.recordLen - 2; if ((len < SSL_MIN_CHALLENGE_BYTES) || (len > SSL_MAX_CHALLENGE_BYTES)) { /* Bad challenge */ SSL_DBG(("%d: SSL[%d]: bad cert request message: code len=%d", SSL_GETPID(), ss->fd, len)); goto bad_peer; } /* save auth request info */ ss->sec.ci.authType = data[1]; ss->sec.ci.serverChallengeLen = len; PORT_Memcpy(ss->sec.ci.serverChallenge, data + 2, len); rv = ssl2_HandleRequestCertificate(ss); if (rv == SECWouldBlock) { SSL_TRC(3, ("%d: SSL[%d]: async cert request", SSL_GETPID(), ss->fd)); /* someone is handling this asynchronously */ ssl_ReleaseRecvBufLock(ss); return SECWouldBlock; } if (rv) { SET_ERROR_CODE goto loser; } break; case SSL_MT_CLIENT_CERTIFICATE: if (!ss->authCertificate) { /* Server asked for authentication and can't handle it */ PORT_SetError(SSL_ERROR_BAD_SERVER); goto loser; } if (ss->gs.recordLen < SSL_HL_CLIENT_CERTIFICATE_HBYTES) { SET_ERROR_CODE goto loser; } certType = data[1]; certLen = (data[2] << 8) | data[3]; responseLen = (data[4] << 8) | data[5]; if (certType != SSL_CT_X509_CERTIFICATE) { PORT_SetError(SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE); goto loser; } if (certLen + responseLen + SSL_HL_CLIENT_CERTIFICATE_HBYTES > ss->gs.recordLen) { /* prevent overflow crash. */ rv = SECFailure; } else rv = ssl2_HandleClientCertificate(ss, data[1], data + SSL_HL_CLIENT_CERTIFICATE_HBYTES, certLen, data + SSL_HL_CLIENT_CERTIFICATE_HBYTES + certLen, responseLen); if (rv) { rv2 = ssl2_SendErrorMessage(ss, SSL_PE_BAD_CERTIFICATE); SET_ERROR_CODE goto loser; } ss->sec.ci.elements |= CIS_HAVE_CERTIFICATE; break; case SSL_MT_ERROR: rv = (data[1] << 8) | data[2]; SSL_TRC(2, ("%d: SSL[%d]: got error message, error=0x%x", SSL_GETPID(), ss->fd, rv)); /* Convert protocol error number into API error number */ switch (rv) { case SSL_PE_NO_CYPHERS: rv = SSL_ERROR_NO_CYPHER_OVERLAP; break; case SSL_PE_NO_CERTIFICATE: rv = SSL_ERROR_NO_CERTIFICATE; break; case SSL_PE_BAD_CERTIFICATE: rv = SSL_ERROR_BAD_CERTIFICATE; break; case SSL_PE_UNSUPPORTED_CERTIFICATE_TYPE: rv = SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE; break; default: goto bad_peer; } /* XXX make certificate-request optionally fail... */ PORT_SetError(rv); goto loser; default: SSL_DBG(("%d: SSL[%d]: unknown message %d", SSL_GETPID(), ss->fd, data[0])); goto loser; } SSL_TRC(3, ("%d: SSL[%d]: handled %d message, required=0x%x got=0x%x", SSL_GETPID(), ss->fd, data[0], ss->sec.ci.requiredElements, ss->sec.ci.elements)); rv = ssl2_TryToFinish(ss); if (rv != SECSuccess) goto loser; ss->gs.recordLen = 0; ssl_ReleaseRecvBufLock(ss); if (ss->handshake == 0) { return SECSuccess; } ss->handshake = ssl_GatherRecord1stHandshake; ss->nextHandshake = ssl2_HandleMessage; return ssl2_TriggerNextMessage(ss); bad_peer: PORT_SetError(ss->sec.isServer ? SSL_ERROR_BAD_CLIENT : SSL_ERROR_BAD_SERVER); /* FALL THROUGH */ loser: ssl_ReleaseRecvBufLock(ss); return SECFailure; } /************************************************************************/ /* Called from ssl_Do1stHandshake, after ssl2_HandleServerHelloMessage. */ static SECStatus ssl2_HandleVerifyMessage(sslSocket *ss) { PRUint8 * data; SECStatus rv; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); ssl_GetRecvBufLock(ss); data = ss->gs.buf.buf + ss->gs.recordOffset; DUMP_MSG(29, (ss, data, ss->gs.recordLen)); if ((ss->gs.recordLen != 1 + SSL_CHALLENGE_BYTES) || (data[0] != SSL_MT_SERVER_VERIFY) || NSS_SecureMemcmp(data+1, ss->sec.ci.clientChallenge, SSL_CHALLENGE_BYTES)) { /* Bad server */ PORT_SetError(SSL_ERROR_BAD_SERVER); goto loser; } ss->sec.ci.elements |= CIS_HAVE_VERIFY; SSL_TRC(5, ("%d: SSL[%d]: got server-verify, required=0x%d got=0x%x", SSL_GETPID(), ss->fd, ss->sec.ci.requiredElements, ss->sec.ci.elements)); rv = ssl2_TryToFinish(ss); if (rv) goto loser; ss->gs.recordLen = 0; ssl_ReleaseRecvBufLock(ss); if (ss->handshake == 0) { return SECSuccess; } ss->handshake = ssl_GatherRecord1stHandshake; ss->nextHandshake = ssl2_HandleMessage; return SECSuccess; loser: ssl_ReleaseRecvBufLock(ss); return SECFailure; } /* Not static because ssl2_GatherData() tests ss->nextHandshake for this value. * ICK! * Called from ssl_Do1stHandshake after ssl2_BeginClientHandshake() */ SECStatus ssl2_HandleServerHelloMessage(sslSocket *ss) { sslSessionID * sid; PRUint8 * cert; PRUint8 * cs; PRUint8 * data; SECStatus rv; int needed, sidHit, certLen, csLen, cidLen, certType, err; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); if (!ss->opt.enableSSL2) { PORT_SetError(SSL_ERROR_SSL2_DISABLED); return SECFailure; } ssl_GetRecvBufLock(ss); PORT_Assert(ss->sec.ci.sid != 0); sid = ss->sec.ci.sid; data = ss->gs.buf.buf + ss->gs.recordOffset; DUMP_MSG(29, (ss, data, ss->gs.recordLen)); /* Make sure first message has some data and is the server hello message */ if ((ss->gs.recordLen < SSL_HL_SERVER_HELLO_HBYTES) || (data[0] != SSL_MT_SERVER_HELLO)) { if ((data[0] == SSL_MT_ERROR) && (ss->gs.recordLen == 3)) { err = (data[1] << 8) | data[2]; if (err == SSL_PE_NO_CYPHERS) { PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP); goto loser; } } goto bad_server; } sidHit = data[1]; certType = data[2]; ss->version = (data[3] << 8) | data[4]; certLen = (data[5] << 8) | data[6]; csLen = (data[7] << 8) | data[8]; cidLen = (data[9] << 8) | data[10]; cert = data + SSL_HL_SERVER_HELLO_HBYTES; cs = cert + certLen; SSL_TRC(5, ("%d: SSL[%d]: server-hello, hit=%d vers=%x certLen=%d csLen=%d cidLen=%d", SSL_GETPID(), ss->fd, sidHit, ss->version, certLen, csLen, cidLen)); if (ss->version != SSL_LIBRARY_VERSION_2) { if (ss->version < SSL_LIBRARY_VERSION_2) { SSL_TRC(3, ("%d: SSL[%d]: demoting self (%x) to server version (%x)", SSL_GETPID(), ss->fd, SSL_LIBRARY_VERSION_2, ss->version)); } else { SSL_TRC(1, ("%d: SSL[%d]: server version is %x (we are %x)", SSL_GETPID(), ss->fd, ss->version, SSL_LIBRARY_VERSION_2)); /* server claims to be newer but does not follow protocol */ PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION); goto loser; } } if ((SSL_HL_SERVER_HELLO_HBYTES + certLen + csLen + cidLen > ss->gs.recordLen) || (csLen % 3) != 0 /* || cidLen < SSL_CONNECTIONID_BYTES || cidLen > 32 */ ) { goto bad_server; } /* Save connection-id. ** This code only saves the first 16 byte of the connectionID. ** If the connectionID is shorter than 16 bytes, it is zero-padded. */ if (cidLen < sizeof ss->sec.ci.connectionID) memset(ss->sec.ci.connectionID, 0, sizeof ss->sec.ci.connectionID); cidLen = PR_MIN(cidLen, sizeof ss->sec.ci.connectionID); PORT_Memcpy(ss->sec.ci.connectionID, cs + csLen, cidLen); /* See if session-id hit */ needed = CIS_HAVE_MASTER_KEY | CIS_HAVE_FINISHED | CIS_HAVE_VERIFY; if (sidHit) { if (certLen || csLen) { /* Uh oh - bogus server */ SSL_DBG(("%d: SSL[%d]: client, huh? hit=%d certLen=%d csLen=%d", SSL_GETPID(), ss->fd, sidHit, certLen, csLen)); goto bad_server; } /* Total winner. */ SSL_TRC(1, ("%d: SSL[%d]: client, using nonce for peer=0x%08x " "port=0x%04x", SSL_GETPID(), ss->fd, ss->sec.ci.peer, ss->sec.ci.port)); ss->sec.peerCert = CERT_DupCertificate(sid->peerCert); ss->sec.authAlgorithm = sid->authAlgorithm; ss->sec.authKeyBits = sid->authKeyBits; ss->sec.keaType = sid->keaType; ss->sec.keaKeyBits = sid->keaKeyBits; rv = ssl2_CreateSessionCypher(ss, sid, PR_TRUE); if (rv != SECSuccess) { goto loser; } } else { if (certType != SSL_CT_X509_CERTIFICATE) { PORT_SetError(SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE); goto loser; } if (csLen == 0) { PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP); SSL_DBG(("%d: SSL[%d]: no cipher overlap", SSL_GETPID(), ss->fd)); goto loser; } if (certLen == 0) { SSL_DBG(("%d: SSL[%d]: client, huh? certLen=%d csLen=%d", SSL_GETPID(), ss->fd, certLen, csLen)); goto bad_server; } if (sid->cached != never_cached) { /* Forget our session-id - server didn't like it */ SSL_TRC(7, ("%d: SSL[%d]: server forgot me, uncaching session-id", SSL_GETPID(), ss->fd)); if (ss->sec.uncache) (*ss->sec.uncache)(sid); ssl_FreeSID(sid); ss->sec.ci.sid = sid = PORT_ZNew(sslSessionID); if (!sid) { goto loser; } sid->references = 1; sid->addr = ss->sec.ci.peer; sid->port = ss->sec.ci.port; } /* decode the server's certificate */ rv = ssl2_ClientHandleServerCert(ss, cert, certLen); if (rv != SECSuccess) { if (PORT_GetError() == SSL_ERROR_BAD_CERTIFICATE) { (void) ssl2_SendErrorMessage(ss, SSL_PE_BAD_CERTIFICATE); } goto loser; } /* Setup new session cipher */ rv = ssl2_ClientSetupSessionCypher(ss, cs, csLen); if (rv != SECSuccess) { if (PORT_GetError() == SSL_ERROR_BAD_CERTIFICATE) { (void) ssl2_SendErrorMessage(ss, SSL_PE_BAD_CERTIFICATE); } goto loser; } } /* Build up final list of required elements */ ss->sec.ci.elements = CIS_HAVE_MASTER_KEY; ss->sec.ci.requiredElements = needed; if (!sidHit) { /* verify the server's certificate. if sidHit, don't check signatures */ rv = (* ss->authCertificate)(ss->authCertificateArg, ss->fd, (PRBool)(!sidHit), PR_FALSE); if (rv) { if (ss->handleBadCert) { rv = (*ss->handleBadCert)(ss->badCertArg, ss->fd); if ( rv ) { if ( rv == SECWouldBlock ) { SSL_DBG(("%d: SSL[%d]: SSL2 bad cert handler returned " "SECWouldBlock", SSL_GETPID(), ss->fd)); PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2); rv = SECFailure; } else { /* cert is bad */ SSL_DBG(("%d: SSL[%d]: server certificate is no good: error=%d", SSL_GETPID(), ss->fd, PORT_GetError())); } goto loser; } /* cert is good */ } else { SSL_DBG(("%d: SSL[%d]: server certificate is no good: error=%d", SSL_GETPID(), ss->fd, PORT_GetError())); goto loser; } } } /* ** At this point we have a completed session key and our session ** cipher is setup and ready to go. Switch to encrypted write routine ** as all future message data is to be encrypted. */ ssl2_UseEncryptedSendFunc(ss); rv = ssl2_TryToFinish(ss); if (rv != SECSuccess) goto loser; ss->gs.recordLen = 0; ssl_ReleaseRecvBufLock(ss); if (ss->handshake == 0) { return SECSuccess; } SSL_TRC(5, ("%d: SSL[%d]: got server-hello, required=0x%d got=0x%x", SSL_GETPID(), ss->fd, ss->sec.ci.requiredElements, ss->sec.ci.elements)); ss->handshake = ssl_GatherRecord1stHandshake; ss->nextHandshake = ssl2_HandleVerifyMessage; return SECSuccess; bad_server: PORT_SetError(SSL_ERROR_BAD_SERVER); /* FALL THROUGH */ loser: ssl_ReleaseRecvBufLock(ss); return SECFailure; } /* Sends out the initial client Hello message on the connection. * Acquires and releases the socket's xmitBufLock. */ SECStatus ssl2_BeginClientHandshake(sslSocket *ss) { sslSessionID *sid; PRUint8 *msg; PRUint8 *cp; PRUint8 *localCipherSpecs = NULL; unsigned int localCipherSize; unsigned int i; int sendLen, sidLen = 0; SECStatus rv; TLSExtensionData *xtnData; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); ss->sec.isServer = 0; ss->sec.sendSequence = 0; ss->sec.rcvSequence = 0; ssl_ChooseSessionIDProcs(&ss->sec); if (!ss->cipherSpecs) { rv = ssl2_ConstructCipherSpecs(ss); if (rv != SECSuccess) goto loser; } /* count the SSL2 and SSL3 enabled ciphers. * if either is zero, clear the socket's enable for that protocol. */ rv = ssl2_CheckConfigSanity(ss); if (rv != SECSuccess) goto loser; /* Get peer name of server */ rv = ssl_GetPeerInfo(ss); if (rv < 0) { #ifdef HPUX11 /* * On some HP-UX B.11.00 systems, getpeername() occasionally * fails with ENOTCONN after a successful completion of * non-blocking connect. I found that if we do a write() * and then retry getpeername(), it will work. */ if (PR_GetError() == PR_NOT_CONNECTED_ERROR) { char dummy; (void) PR_Write(ss->fd->lower, &dummy, 0); rv = ssl_GetPeerInfo(ss); if (rv < 0) { goto loser; } } #else goto loser; #endif } SSL_TRC(3, ("%d: SSL[%d]: sending client-hello", SSL_GETPID(), ss->fd)); /* Try to find server in our session-id cache */ if (ss->opt.noCache) { sid = NULL; } else { sid = ssl_LookupSID(&ss->sec.ci.peer, ss->sec.ci.port, ss->peerID, ss->url); } while (sid) { /* this isn't really a loop */ PRBool sidVersionEnabled = (!SSL3_ALL_VERSIONS_DISABLED(&ss->vrange) && sid->version >= ss->vrange.min && sid->version <= ss->vrange.max) || (sid->version < SSL_LIBRARY_VERSION_3_0 && ss->opt.enableSSL2); /* if we're not doing this SID's protocol any more, drop it. */ if (!sidVersionEnabled) { if (ss->sec.uncache) ss->sec.uncache(sid); ssl_FreeSID(sid); sid = NULL; break; } if (sid->version < SSL_LIBRARY_VERSION_3_0) { /* If the cipher in this sid is not enabled, drop it. */ for (i = 0; i < ss->sizeCipherSpecs; i += 3) { if (ss->cipherSpecs[i] == sid->u.ssl2.cipherType) break; } if (i >= ss->sizeCipherSpecs) { if (ss->sec.uncache) ss->sec.uncache(sid); ssl_FreeSID(sid); sid = NULL; break; } } sidLen = sizeof(sid->u.ssl2.sessionID); PRINT_BUF(4, (ss, "client, found session-id:", sid->u.ssl2.sessionID, sidLen)); ss->version = sid->version; PORT_Assert(!ss->sec.localCert); if (ss->sec.localCert) { CERT_DestroyCertificate(ss->sec.localCert); } ss->sec.localCert = CERT_DupCertificate(sid->localCert); break; /* this isn't really a loop */ } if (!sid) { sidLen = 0; sid = PORT_ZNew(sslSessionID); if (!sid) { goto loser; } sid->references = 1; sid->cached = never_cached; sid->addr = ss->sec.ci.peer; sid->port = ss->sec.ci.port; if (ss->peerID != NULL) { sid->peerID = PORT_Strdup(ss->peerID); } if (ss->url != NULL) { sid->urlSvrName = PORT_Strdup(ss->url); } } ss->sec.ci.sid = sid; PORT_Assert(sid != NULL); if ((sid->version >= SSL_LIBRARY_VERSION_3_0 || !ss->opt.v2CompatibleHello) && !SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) { ss->gs.state = GS_INIT; ss->handshake = ssl_GatherRecord1stHandshake; /* ssl3_SendClientHello will override this if it succeeds. */ ss->version = SSL_LIBRARY_VERSION_3_0; ssl_GetSSL3HandshakeLock(ss); ssl_GetXmitBufLock(ss); rv = ssl3_SendClientHello(ss, PR_FALSE); ssl_ReleaseXmitBufLock(ss); ssl_ReleaseSSL3HandshakeLock(ss); return rv; } #if defined(NSS_ENABLE_ECC) /* ensure we don't neogtiate ECC cipher suites with SSL2 hello */ ssl3_DisableECCSuites(ss, NULL); /* disable all ECC suites */ if (ss->cipherSpecs != NULL) { PORT_Free(ss->cipherSpecs); ss->cipherSpecs = NULL; ss->sizeCipherSpecs = 0; } #endif if (!ss->cipherSpecs) { rv = ssl2_ConstructCipherSpecs(ss); if (rv < 0) { return rv; } } localCipherSpecs = ss->cipherSpecs; localCipherSize = ss->sizeCipherSpecs; /* Add 3 for SCSV */ sendLen = SSL_HL_CLIENT_HELLO_HBYTES + localCipherSize + 3 + sidLen + SSL_CHALLENGE_BYTES; /* Generate challenge bytes for server */ PK11_GenerateRandom(ss->sec.ci.clientChallenge, SSL_CHALLENGE_BYTES); ssl_GetXmitBufLock(ss); /***************************************/ rv = ssl2_GetSendBuffer(ss, sendLen); if (rv) goto unlock_loser; /* Construct client-hello message */ cp = msg = ss->sec.ci.sendBuf.buf; msg[0] = SSL_MT_CLIENT_HELLO; ss->clientHelloVersion = SSL3_ALL_VERSIONS_DISABLED(&ss->vrange) ? SSL_LIBRARY_VERSION_2 : ss->vrange.max; msg[1] = MSB(ss->clientHelloVersion); msg[2] = LSB(ss->clientHelloVersion); /* Add 3 for SCSV */ msg[3] = MSB(localCipherSize + 3); msg[4] = LSB(localCipherSize + 3); msg[5] = MSB(sidLen); msg[6] = LSB(sidLen); msg[7] = MSB(SSL_CHALLENGE_BYTES); msg[8] = LSB(SSL_CHALLENGE_BYTES); cp += SSL_HL_CLIENT_HELLO_HBYTES; PORT_Memcpy(cp, localCipherSpecs, localCipherSize); cp += localCipherSize; /* * Add SCSV. SSL 2.0 cipher suites are listed before SSL 3.0 cipher * suites in localCipherSpecs for compatibility with SSL 2.0 servers. * Since SCSV looks like an SSL 3.0 cipher suite, we can't add it at * the beginning. */ cp[0] = 0x00; cp[1] = 0x00; cp[2] = 0xff; cp += 3; if (sidLen) { PORT_Memcpy(cp, sid->u.ssl2.sessionID, sidLen); cp += sidLen; } PORT_Memcpy(cp, ss->sec.ci.clientChallenge, SSL_CHALLENGE_BYTES); /* Send it to the server */ DUMP_MSG(29, (ss, msg, sendLen)); ss->handshakeBegun = 1; rv = (*ss->sec.send)(ss, msg, sendLen, 0); ssl_ReleaseXmitBufLock(ss); /***************************************/ if (rv < 0) { goto loser; } rv = ssl3_StartHandshakeHash(ss, msg, sendLen); if (rv < 0) { goto loser; } /* * Since we sent the SCSV, pretend we sent empty RI extension. We need * to record the extension has been advertised after ssl3_InitState has * been called, which ssl3_StartHandshakeHash took care for us above. */ xtnData = &ss->xtnData; xtnData->advertised[xtnData->numAdvertised++] = ssl_renegotiation_info_xtn; /* Setup to receive servers hello message */ ssl_GetRecvBufLock(ss); ss->gs.recordLen = 0; ssl_ReleaseRecvBufLock(ss); ss->handshake = ssl_GatherRecord1stHandshake; ss->nextHandshake = ssl2_HandleServerHelloMessage; return SECSuccess; unlock_loser: ssl_ReleaseXmitBufLock(ss); loser: return SECFailure; } /************************************************************************/ /* Handle the CLIENT-MASTER-KEY message. ** Acquires and releases RecvBufLock. ** Called from ssl2_HandleClientHelloMessage(). */ static SECStatus ssl2_HandleClientSessionKeyMessage(sslSocket *ss) { PRUint8 * data; unsigned int caLen; unsigned int ckLen; unsigned int ekLen; unsigned int keyBits; int cipher; SECStatus rv; ssl_GetRecvBufLock(ss); data = ss->gs.buf.buf + ss->gs.recordOffset; DUMP_MSG(29, (ss, data, ss->gs.recordLen)); if ((ss->gs.recordLen < SSL_HL_CLIENT_MASTER_KEY_HBYTES) || (data[0] != SSL_MT_CLIENT_MASTER_KEY)) { goto bad_client; } cipher = data[1]; keyBits = (data[2] << 8) | data[3]; ckLen = (data[4] << 8) | data[5]; ekLen = (data[6] << 8) | data[7]; caLen = (data[8] << 8) | data[9]; SSL_TRC(5, ("%d: SSL[%d]: session-key, cipher=%d keyBits=%d ckLen=%d ekLen=%d caLen=%d", SSL_GETPID(), ss->fd, cipher, keyBits, ckLen, ekLen, caLen)); if (ss->gs.recordLen < SSL_HL_CLIENT_MASTER_KEY_HBYTES + ckLen + ekLen + caLen) { SSL_DBG(("%d: SSL[%d]: protocol size mismatch dataLen=%d", SSL_GETPID(), ss->fd, ss->gs.recordLen)); goto bad_client; } /* Use info from client to setup session key */ rv = ssl2_ServerSetupSessionCypher(ss, cipher, keyBits, data + SSL_HL_CLIENT_MASTER_KEY_HBYTES, ckLen, data + SSL_HL_CLIENT_MASTER_KEY_HBYTES + ckLen, ekLen, data + SSL_HL_CLIENT_MASTER_KEY_HBYTES + ckLen + ekLen, caLen); ss->gs.recordLen = 0; /* we're done with this record. */ ssl_ReleaseRecvBufLock(ss); if (rv != SECSuccess) { goto loser; } ss->sec.ci.elements |= CIS_HAVE_MASTER_KEY; ssl2_UseEncryptedSendFunc(ss); /* Send server verify message now that keys are established */ rv = ssl2_SendServerVerifyMessage(ss); if (rv != SECSuccess) goto loser; rv = ssl2_TryToFinish(ss); if (rv != SECSuccess) goto loser; if (ss->handshake == 0) { return SECSuccess; } SSL_TRC(5, ("%d: SSL[%d]: server: waiting for elements=0x%d", SSL_GETPID(), ss->fd, ss->sec.ci.requiredElements ^ ss->sec.ci.elements)); ss->handshake = ssl_GatherRecord1stHandshake; ss->nextHandshake = ssl2_HandleMessage; return ssl2_TriggerNextMessage(ss); bad_client: ssl_ReleaseRecvBufLock(ss); PORT_SetError(SSL_ERROR_BAD_CLIENT); /* FALLTHROUGH */ loser: return SECFailure; } /* ** Handle the initial hello message from the client ** ** not static because ssl2_GatherData() tests ss->nextHandshake for this value. */ SECStatus ssl2_HandleClientHelloMessage(sslSocket *ss) { sslSessionID *sid; sslServerCerts * sc; CERTCertificate *serverCert; PRUint8 *msg; PRUint8 *data; PRUint8 *cs; PRUint8 *sd; PRUint8 *cert = NULL; PRUint8 *challenge; unsigned int challengeLen; SECStatus rv; int csLen; int sendLen; int sdLen; int certLen; int pid; int sent; int gotXmitBufLock = 0; #if defined(SOLARIS) && defined(i386) volatile PRUint8 hit; #else int hit; #endif PRUint8 csImpl[sizeof implementedCipherSuites]; PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) ); sc = ss->serverCerts + kt_rsa; serverCert = sc->serverCert; ssl_GetRecvBufLock(ss); data = ss->gs.buf.buf + ss->gs.recordOffset; DUMP_MSG(29, (ss, data, ss->gs.recordLen)); /* Make sure first message has some data and is the client hello message */ if ((ss->gs.recordLen < SSL_HL_CLIENT_HELLO_HBYTES) || (data[0] != SSL_MT_CLIENT_HELLO)) { goto bad_client; } /* Get peer name of client */ rv = ssl_GetPeerInfo(ss); if (rv != SECSuccess) { goto loser; } /* Examine version information */ /* * See if this might be a V2 client hello asking to use the V3 protocol */ if ((data[0] == SSL_MT_CLIENT_HELLO) && (data[1] >= MSB(SSL_LIBRARY_VERSION_3_0)) && !SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) { rv = ssl3_HandleV2ClientHello(ss, data, ss->gs.recordLen); if (rv != SECFailure) { /* Success */ ss->handshake = NULL; ss->nextHandshake = ssl_GatherRecord1stHandshake; ss->securityHandshake = NULL; ss->gs.state = GS_INIT; /* ssl3_HandleV3ClientHello has set ss->version, ** and has gotten us a brand new sid. */ ss->sec.ci.sid->version = ss->version; } ssl_ReleaseRecvBufLock(ss); return rv; } /* Previously, there was a test here to see if SSL2 was enabled. ** If not, an error code was set, and SECFailure was returned, ** without sending any error code to the other end of the connection. ** That test has been removed. If SSL2 has been disabled, there ** should be no SSL2 ciphers enabled, and consequently, the code ** below should send the ssl2 error message SSL_PE_NO_CYPHERS. ** We now believe this is the correct thing to do, even when SSL2 ** has been explicitly disabled by the application. */ /* Extract info from message */ ss->version = (data[1] << 8) | data[2]; /* If some client thinks ssl v2 is 2.0 instead of 0.2, we'll allow it. */ if (ss->version >= SSL_LIBRARY_VERSION_3_0) { ss->version = SSL_LIBRARY_VERSION_2; } csLen = (data[3] << 8) | data[4]; sdLen = (data[5] << 8) | data[6]; challengeLen = (data[7] << 8) | data[8]; cs = data + SSL_HL_CLIENT_HELLO_HBYTES; sd = cs + csLen; challenge = sd + sdLen; PRINT_BUF(7, (ss, "server, client session-id value:", sd, sdLen)); if (!csLen || (csLen % 3) != 0 || (sdLen != 0 && sdLen != SSL2_SESSIONID_BYTES) || challengeLen < SSL_MIN_CHALLENGE_BYTES || challengeLen > SSL_MAX_CHALLENGE_BYTES || (unsigned)ss->gs.recordLen != SSL_HL_CLIENT_HELLO_HBYTES + csLen + sdLen + challengeLen) { SSL_DBG(("%d: SSL[%d]: bad client hello message, len=%d should=%d", SSL_GETPID(), ss->fd, ss->gs.recordLen, SSL_HL_CLIENT_HELLO_HBYTES+csLen+sdLen+challengeLen)); goto bad_client; } SSL_TRC(3, ("%d: SSL[%d]: client version is %x", SSL_GETPID(), ss->fd, ss->version)); if (ss->version != SSL_LIBRARY_VERSION_2) { if (ss->version > SSL_LIBRARY_VERSION_2) { /* ** Newer client than us. Things are ok because new clients ** are required to be backwards compatible with old servers. ** Change version number to our version number so that client ** knows whats up. */ ss->version = SSL_LIBRARY_VERSION_2; } else { SSL_TRC(1, ("%d: SSL[%d]: client version is %x (we are %x)", SSL_GETPID(), ss->fd, ss->version, SSL_LIBRARY_VERSION_2)); PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION); goto loser; } } /* Qualify cipher specs before returning them to client */ csLen = ssl2_QualifyCypherSpecs(ss, cs, csLen); if (csLen == 0) { /* no overlap, send client our list of supported SSL v2 ciphers. */ cs = csImpl; csLen = sizeof implementedCipherSuites; PORT_Memcpy(cs, implementedCipherSuites, csLen); csLen = ssl2_QualifyCypherSpecs(ss, cs, csLen); if (csLen == 0) { /* We don't support any SSL v2 ciphers! */ ssl2_SendErrorMessage(ss, SSL_PE_NO_CYPHERS); PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP); goto loser; } /* Since this handhsake is going to fail, don't cache it. */ ss->opt.noCache = 1; } /* Squirrel away the challenge for later */ PORT_Memcpy(ss->sec.ci.clientChallenge, challenge, challengeLen); /* Examine message and see if session-id is good */ ss->sec.ci.elements = 0; if (sdLen > 0 && !ss->opt.noCache) { SSL_TRC(7, ("%d: SSL[%d]: server, lookup client session-id for 0x%08x%08x%08x%08x", SSL_GETPID(), ss->fd, ss->sec.ci.peer.pr_s6_addr32[0], ss->sec.ci.peer.pr_s6_addr32[1], ss->sec.ci.peer.pr_s6_addr32[2], ss->sec.ci.peer.pr_s6_addr32[3])); sid = (*ssl_sid_lookup)(&ss->sec.ci.peer, sd, sdLen, ss->dbHandle); } else { sid = NULL; } if (sid) { /* Got a good session-id. Short cut! */ SSL_TRC(1, ("%d: SSL[%d]: server, using session-id for 0x%08x (age=%d)", SSL_GETPID(), ss->fd, ss->sec.ci.peer, ssl_Time() - sid->creationTime)); PRINT_BUF(1, (ss, "session-id value:", sd, sdLen)); ss->sec.ci.sid = sid; ss->sec.ci.elements = CIS_HAVE_MASTER_KEY; hit = 1; certLen = 0; csLen = 0; ss->sec.authAlgorithm = sid->authAlgorithm; ss->sec.authKeyBits = sid->authKeyBits; ss->sec.keaType = sid->keaType; ss->sec.keaKeyBits = sid->keaKeyBits; rv = ssl2_CreateSessionCypher(ss, sid, PR_FALSE); if (rv != SECSuccess) { goto loser; } } else { SECItem * derCert = &serverCert->derCert; SSL_TRC(7, ("%d: SSL[%d]: server, lookup nonce missed", SSL_GETPID(), ss->fd)); if (!serverCert) { SET_ERROR_CODE goto loser; } hit = 0; sid = PORT_ZNew(sslSessionID); if (!sid) { goto loser; } sid->references = 1; sid->addr = ss->sec.ci.peer; sid->port = ss->sec.ci.port; /* Invent a session-id */ ss->sec.ci.sid = sid; PK11_GenerateRandom(sid->u.ssl2.sessionID+2, SSL2_SESSIONID_BYTES-2); pid = SSL_GETPID(); sid->u.ssl2.sessionID[0] = MSB(pid); sid->u.ssl2.sessionID[1] = LSB(pid); cert = derCert->data; certLen = derCert->len; /* pretend that server sids remember the local cert. */ PORT_Assert(!sid->localCert); if (sid->localCert) { CERT_DestroyCertificate(sid->localCert); } sid->localCert = CERT_DupCertificate(serverCert); ss->sec.authAlgorithm = ssl_sign_rsa; ss->sec.keaType = ssl_kea_rsa; ss->sec.keaKeyBits = \ ss->sec.authKeyBits = ss->serverCerts[kt_rsa].serverKeyBits; } /* server sids don't remember the local cert, so whether we found ** a sid or not, just "remember" we used the rsa server cert. */ if (ss->sec.localCert) { CERT_DestroyCertificate(ss->sec.localCert); } ss->sec.localCert = CERT_DupCertificate(serverCert); /* Build up final list of required elements */ ss->sec.ci.requiredElements = CIS_HAVE_MASTER_KEY | CIS_HAVE_FINISHED; if (ss->opt.requestCertificate) { ss->sec.ci.requiredElements |= CIS_HAVE_CERTIFICATE; } ss->sec.ci.sentElements = 0; /* Send hello message back to client */ sendLen = SSL_HL_SERVER_HELLO_HBYTES + certLen + csLen + SSL_CONNECTIONID_BYTES; ssl_GetXmitBufLock(ss); gotXmitBufLock = 1; rv = ssl2_GetSendBuffer(ss, sendLen); if (rv != SECSuccess) { goto loser; } SSL_TRC(3, ("%d: SSL[%d]: sending server-hello (%d)", SSL_GETPID(), ss->fd, sendLen)); msg = ss->sec.ci.sendBuf.buf; msg[0] = SSL_MT_SERVER_HELLO; msg[1] = hit; msg[2] = SSL_CT_X509_CERTIFICATE; msg[3] = MSB(ss->version); msg[4] = LSB(ss->version); msg[5] = MSB(certLen); msg[6] = LSB(certLen); msg[7] = MSB(csLen); msg[8] = LSB(csLen); msg[9] = MSB(SSL_CONNECTIONID_BYTES); msg[10] = LSB(SSL_CONNECTIONID_BYTES); if (certLen) { PORT_Memcpy(msg+SSL_HL_SERVER_HELLO_HBYTES, cert, certLen); } if (csLen) { PORT_Memcpy(msg+SSL_HL_SERVER_HELLO_HBYTES+certLen, cs, csLen); } PORT_Memcpy(msg+SSL_HL_SERVER_HELLO_HBYTES+certLen+csLen, ss->sec.ci.connectionID, SSL_CONNECTIONID_BYTES); DUMP_MSG(29, (ss, msg, sendLen)); ss->handshakeBegun = 1; sent = (*ss->sec.send)(ss, msg, sendLen, 0); if (sent < 0) { goto loser; } ssl_ReleaseXmitBufLock(ss); gotXmitBufLock = 0; ss->gs.recordLen = 0; ss->handshake = ssl_GatherRecord1stHandshake; if (hit) { /* Old SID Session key is good. Go encrypted */ ssl2_UseEncryptedSendFunc(ss); /* Send server verify message now that keys are established */ rv = ssl2_SendServerVerifyMessage(ss); if (rv != SECSuccess) goto loser; ss->nextHandshake = ssl2_HandleMessage; ssl_ReleaseRecvBufLock(ss); rv = ssl2_TriggerNextMessage(ss); return rv; } ss->nextHandshake = ssl2_HandleClientSessionKeyMessage; ssl_ReleaseRecvBufLock(ss); return SECSuccess; bad_client: PORT_SetError(SSL_ERROR_BAD_CLIENT); /* FALLTHROUGH */ loser: if (gotXmitBufLock) { ssl_ReleaseXmitBufLock(ss); gotXmitBufLock = 0; } SSL_TRC(10, ("%d: SSL[%d]: server, wait for client-hello lossage", SSL_GETPID(), ss->fd)); ssl_ReleaseRecvBufLock(ss); return SECFailure; } SECStatus ssl2_BeginServerHandshake(sslSocket *ss) { SECStatus rv; sslServerCerts * rsaAuth = ss->serverCerts + kt_rsa; ss->sec.isServer = 1; ssl_ChooseSessionIDProcs(&ss->sec); ss->sec.sendSequence = 0; ss->sec.rcvSequence = 0; /* don't turn on SSL2 if we don't have an RSA key and cert */ if (!rsaAuth->serverKeyPair || !rsaAuth->SERVERKEY || !rsaAuth->serverCert) { ss->opt.enableSSL2 = PR_FALSE; } if (!ss->cipherSpecs) { rv = ssl2_ConstructCipherSpecs(ss); if (rv != SECSuccess) goto loser; } /* count the SSL2 and SSL3 enabled ciphers. * if either is zero, clear the socket's enable for that protocol. */ rv = ssl2_CheckConfigSanity(ss); if (rv != SECSuccess) goto loser; /* ** Generate connection-id. Always do this, even if things fail ** immediately. This way the random number generator is always ** rolling around, every time we get a connection. */ PK11_GenerateRandom(ss->sec.ci.connectionID, sizeof(ss->sec.ci.connectionID)); ss->gs.recordLen = 0; ss->handshake = ssl_GatherRecord1stHandshake; ss->nextHandshake = ssl2_HandleClientHelloMessage; return SECSuccess; loser: return SECFailure; } /* This function doesn't really belong in this file. ** It's here to keep AIX compilers from optimizing it away, ** and not including it in the DSO. */ #include "nss.h" extern const char __nss_ssl_rcsid[]; extern const char __nss_ssl_sccsid[]; PRBool NSSSSL_VersionCheck(const char *importedVersion) { /* * This is the secret handshake algorithm. * * This release has a simple version compatibility * check algorithm. This release is not backward * compatible with previous major releases. It is * not compatible with future major, minor, or * patch releases. */ volatile char c; /* force a reference that won't get optimized away */ c = __nss_ssl_rcsid[0] + __nss_ssl_sccsid[0]; return NSS_VersionCheck(importedVersion); } const char * NSSSSL_GetVersion(void) { return NSS_VERSION; }