| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include "vfyserv.h" |
| #include "secerr.h" |
| #include "sslerr.h" |
| #include "nspr.h" |
| #include "secutil.h" |
| |
| extern PRBool dumpChain; |
| extern void dumpCertChain(CERTCertificate *, SECCertUsage); |
| |
| /* Declare SSL cipher suites. */ |
| |
| int ssl3CipherSuites[] = { |
| -1, /* SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA* a */ |
| -1, /* SSL_FORTEZZA_DMS_WITH_RC4_128_SHA * b */ |
| TLS_RSA_WITH_RC4_128_MD5, /* c */ |
| TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* d */ |
| TLS_RSA_WITH_DES_CBC_SHA, /* e */ |
| -1, /* TLS_RSA_EXPORT_WITH_RC4_40_MD5 * f */ |
| -1, /* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 * g */ |
| -1, /* SSL_FORTEZZA_DMS_WITH_NULL_SHA * h */ |
| TLS_RSA_WITH_NULL_MD5, /* i */ |
| -1, /* SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA * j */ |
| -1, /* SSL_RSA_FIPS_WITH_DES_CBC_SHA * k */ |
| -1, /* TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA * l */ |
| -1, /* TLS_RSA_EXPORT1024_WITH_RC4_56_SHA * m */ |
| TLS_RSA_WITH_RC4_128_SHA, /* n */ |
| TLS_DHE_DSS_WITH_RC4_128_SHA, /* o */ |
| TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, /* p */ |
| TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, /* q */ |
| TLS_DHE_RSA_WITH_DES_CBC_SHA, /* r */ |
| TLS_DHE_DSS_WITH_DES_CBC_SHA, /* s */ |
| TLS_DHE_DSS_WITH_AES_128_CBC_SHA, /* t */ |
| TLS_DHE_RSA_WITH_AES_128_CBC_SHA, /* u */ |
| TLS_RSA_WITH_AES_128_CBC_SHA, /* v */ |
| TLS_DHE_DSS_WITH_AES_256_CBC_SHA, /* w */ |
| TLS_DHE_RSA_WITH_AES_256_CBC_SHA, /* x */ |
| TLS_RSA_WITH_AES_256_CBC_SHA, /* y */ |
| TLS_RSA_WITH_NULL_SHA, /* z */ |
| 0 |
| }; |
| int numSSL3CipherSuites = PR_ARRAY_SIZE(ssl3CipherSuites); |
| |
| /************************************************************************** |
| ** |
| ** SSL callback routines. |
| ** |
| **************************************************************************/ |
| |
| /* Function: char * myPasswd() |
| * |
| * Purpose: This function is our custom password handler that is called by |
| * SSL when retreiving private certs and keys from the database. Returns a |
| * pointer to a string that with a password for the database. Password pointer |
| * should point to dynamically allocated memory that will be freed later. |
| */ |
| char * |
| myPasswd(PK11SlotInfo *info, PRBool retry, void *arg) |
| { |
| char *passwd = NULL; |
| |
| if ((!retry) && arg) { |
| passwd = PORT_Strdup((char *)arg); |
| } |
| return passwd; |
| } |
| |
| /* Function: SECStatus myAuthCertificate() |
| * |
| * Purpose: This function is our custom certificate authentication handler. |
| * |
| * Note: This implementation is essentially the same as the default |
| * SSL_AuthCertificate(). |
| */ |
| SECStatus |
| myAuthCertificate(void *arg, PRFileDesc *socket, |
| PRBool checksig, PRBool isServer) |
| { |
| |
| SECCertificateUsage certUsage; |
| CERTCertificate *cert; |
| void *pinArg; |
| char *hostName; |
| SECStatus secStatus; |
| |
| if (!arg || !socket) { |
| errWarn("myAuthCertificate"); |
| return SECFailure; |
| } |
| |
| /* Define how the cert is being used based upon the isServer flag. */ |
| |
| certUsage = isServer ? certificateUsageSSLClient : certificateUsageSSLServer; |
| |
| cert = SSL_PeerCertificate(socket); |
| |
| pinArg = SSL_RevealPinArg(socket); |
| |
| if (dumpChain == PR_TRUE) { |
| dumpCertChain(cert, certUsage); |
| } |
| |
| secStatus = CERT_VerifyCertificateNow((CERTCertDBHandle *)arg, |
| cert, |
| checksig, |
| certUsage, |
| pinArg, |
| NULL); |
| |
| /* If this is a server, we're finished. */ |
| if (isServer || secStatus != SECSuccess) { |
| SECU_printCertProblems(stderr, (CERTCertDBHandle *)arg, cert, |
| checksig, certUsage, pinArg, PR_FALSE); |
| CERT_DestroyCertificate(cert); |
| return secStatus; |
| } |
| |
| /* Certificate is OK. Since this is the client side of an SSL |
| * connection, we need to verify that the name field in the cert |
| * matches the desired hostname. This is our defense against |
| * man-in-the-middle attacks. |
| */ |
| |
| /* SSL_RevealURL returns a hostName, not an URL. */ |
| hostName = SSL_RevealURL(socket); |
| |
| if (hostName && hostName[0]) { |
| secStatus = CERT_VerifyCertName(cert, hostName); |
| } else { |
| PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0); |
| secStatus = SECFailure; |
| } |
| |
| if (hostName) |
| PR_Free(hostName); |
| |
| CERT_DestroyCertificate(cert); |
| return secStatus; |
| } |
| |
| /* Function: SECStatus myBadCertHandler() |
| * |
| * Purpose: This callback is called when the incoming certificate is not |
| * valid. We define a certain set of parameters that still cause the |
| * certificate to be "valid" for this session, and return SECSuccess to cause |
| * the server to continue processing the request when any of these conditions |
| * are met. Otherwise, SECFailure is return and the server rejects the |
| * request. |
| */ |
| SECStatus |
| myBadCertHandler(void *arg, PRFileDesc *socket) |
| { |
| |
| SECStatus secStatus = SECFailure; |
| PRErrorCode err; |
| |
| /* log invalid cert here */ |
| |
| if (!arg) { |
| return secStatus; |
| } |
| |
| *(PRErrorCode *)arg = err = PORT_GetError(); |
| |
| /* If any of the cases in the switch are met, then we will proceed */ |
| /* with the processing of the request anyway. Otherwise, the default */ |
| /* case will be reached and we will reject the request. */ |
| |
| switch (err) { |
| case SEC_ERROR_INVALID_AVA: |
| case SEC_ERROR_INVALID_TIME: |
| case SEC_ERROR_BAD_SIGNATURE: |
| case SEC_ERROR_EXPIRED_CERTIFICATE: |
| case SEC_ERROR_UNKNOWN_ISSUER: |
| case SEC_ERROR_UNTRUSTED_CERT: |
| case SEC_ERROR_CERT_VALID: |
| case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: |
| case SEC_ERROR_CRL_EXPIRED: |
| case SEC_ERROR_CRL_BAD_SIGNATURE: |
| case SEC_ERROR_EXTENSION_VALUE_INVALID: |
| case SEC_ERROR_CA_CERT_INVALID: |
| case SEC_ERROR_CERT_USAGES_INVALID: |
| case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: |
| secStatus = SECSuccess; |
| break; |
| default: |
| secStatus = SECFailure; |
| break; |
| } |
| |
| fprintf(stderr, "Bad certificate: %d, %s\n", err, SECU_Strerror(err)); |
| |
| return secStatus; |
| } |
| |
| /* Function: SECStatus ownGetClientAuthData() |
| * |
| * Purpose: This callback is used by SSL to pull client certificate |
| * information upon server request. |
| */ |
| SECStatus |
| myGetClientAuthData(void *arg, |
| PRFileDesc *socket, |
| struct CERTDistNamesStr *caNames, |
| struct CERTCertificateStr **pRetCert, |
| struct SECKEYPrivateKeyStr **pRetKey) |
| { |
| |
| CERTCertificate *cert; |
| SECKEYPrivateKey *privKey; |
| char *chosenNickName = (char *)arg; |
| void *proto_win = NULL; |
| SECStatus secStatus = SECFailure; |
| |
| proto_win = SSL_RevealPinArg(socket); |
| |
| if (chosenNickName) { |
| cert = PK11_FindCertFromNickname(chosenNickName, proto_win); |
| if (cert) { |
| privKey = PK11_FindKeyByAnyCert(cert, proto_win); |
| if (privKey) { |
| secStatus = SECSuccess; |
| } else { |
| CERT_DestroyCertificate(cert); |
| } |
| } |
| } else { /* no nickname given, automatically find the right cert */ |
| CERTCertNicknames *names; |
| int i; |
| |
| names = CERT_GetCertNicknames(CERT_GetDefaultCertDB(), |
| SEC_CERT_NICKNAMES_USER, proto_win); |
| |
| if (names != NULL) { |
| for (i = 0; i < names->numnicknames; i++) { |
| |
| cert = PK11_FindCertFromNickname(names->nicknames[i], |
| proto_win); |
| if (!cert) { |
| continue; |
| } |
| |
| /* Only check unexpired certs */ |
| if (CERT_CheckCertValidTimes(cert, PR_Now(), PR_FALSE) != |
| secCertTimeValid) { |
| CERT_DestroyCertificate(cert); |
| continue; |
| } |
| |
| secStatus = NSS_CmpCertChainWCANames(cert, caNames); |
| if (secStatus == SECSuccess) { |
| privKey = PK11_FindKeyByAnyCert(cert, proto_win); |
| if (privKey) { |
| break; |
| } |
| secStatus = SECFailure; |
| } |
| CERT_DestroyCertificate(cert); |
| } /* for loop */ |
| CERT_FreeNicknames(names); |
| } |
| } |
| |
| if (secStatus == SECSuccess) { |
| *pRetCert = cert; |
| *pRetKey = privKey; |
| } |
| |
| return secStatus; |
| } |
| |
| /* Function: void myHandshakeCallback() |
| * |
| * Purpose: Called by SSL to inform application that the handshake is |
| * complete. This function is mostly used on the server side of an SSL |
| * connection, although it is provided for a client as well. |
| * Useful when a non-blocking SSL_ReHandshake or SSL_ResetHandshake |
| * is used to initiate a handshake. |
| * |
| * A typical scenario would be: |
| * |
| * 1. Server accepts an SSL connection from the client without client auth. |
| * 2. Client sends a request. |
| * 3. Server determines that to service request it needs to authenticate the |
| * client and initiates another handshake requesting client auth. |
| * 4. While handshake is in progress, server can do other work or spin waiting |
| * for the handshake to complete. |
| * 5. Server is notified that handshake has been successfully completed by |
| * the custom handshake callback function and it can service the client's |
| * request. |
| * |
| * Note: This function is not implemented in this sample, as we are using |
| * blocking sockets. |
| */ |
| void |
| myHandshakeCallback(PRFileDesc *socket, void *arg) |
| { |
| fprintf(stderr, "Handshake Complete: SERVER CONFIGURED CORRECTLY\n"); |
| } |
| |
| /************************************************************************** |
| ** |
| ** Routines for disabling SSL ciphers. |
| ** |
| **************************************************************************/ |
| |
| void |
| disableAllSSLCiphers(void) |
| { |
| const PRUint16 *allSuites = SSL_ImplementedCiphers; |
| int i = SSL_NumImplementedCiphers; |
| SECStatus rv; |
| |
| /* disable all the SSL3 cipher suites */ |
| while (--i >= 0) { |
| PRUint16 suite = allSuites[i]; |
| rv = SSL_CipherPrefSetDefault(suite, PR_FALSE); |
| if (rv != SECSuccess) { |
| fprintf(stderr, |
| "SSL_CipherPrefSetDefault didn't like value 0x%04x (i = %d)\n", |
| suite, i); |
| errWarn("SSL_CipherPrefSetDefault"); |
| exit(2); |
| } |
| } |
| } |
| |
| /************************************************************************** |
| ** |
| ** Error and information routines. |
| ** |
| **************************************************************************/ |
| |
| void |
| errWarn(char *function) |
| { |
| PRErrorCode errorNumber = PR_GetError(); |
| const char *errorString = SECU_Strerror(errorNumber); |
| |
| fprintf(stderr, "Error in function %s: %d\n - %s\n", |
| function, errorNumber, errorString); |
| } |
| |
| void |
| exitErr(char *function) |
| { |
| errWarn(function); |
| /* Exit gracefully. */ |
| /* ignoring return value of NSS_Shutdown as code exits with 1 anyway*/ |
| (void)NSS_Shutdown(); |
| PR_Cleanup(); |
| exit(1); |
| } |
| |
| void |
| printSecurityInfo(FILE *outfile, PRFileDesc *fd) |
| { |
| char *cp; /* bulk cipher name */ |
| char *ip; /* cert issuer DN */ |
| char *sp; /* cert subject DN */ |
| int op; /* High, Low, Off */ |
| int kp0; /* total key bits */ |
| int kp1; /* secret key bits */ |
| int result; |
| SSL3Statistics *ssl3stats = SSL_GetStatistics(); |
| |
| if (!outfile) { |
| outfile = stdout; |
| } |
| |
| result = SSL_SecurityStatus(fd, &op, &cp, &kp0, &kp1, &ip, &sp); |
| if (result != SECSuccess) |
| return; |
| fprintf(outfile, |
| " bulk cipher %s, %d secret key bits, %d key bits, status: %d\n" |
| " subject DN:\n %s\n" |
| " issuer DN:\n %s\n", |
| cp, kp1, kp0, op, sp, ip); |
| PR_Free(cp); |
| PR_Free(ip); |
| PR_Free(sp); |
| |
| fprintf(outfile, |
| " %ld cache hits; %ld cache misses, %ld cache not reusable\n", |
| ssl3stats->hch_sid_cache_hits, ssl3stats->hch_sid_cache_misses, |
| ssl3stats->hch_sid_cache_not_ok); |
| } |
| |
| /************************************************************************** |
| ** Begin thread management routines and data. |
| **************************************************************************/ |
| |
| void |
| thread_wrapper(void *arg) |
| { |
| GlobalThreadMgr *threadMGR = (GlobalThreadMgr *)arg; |
| perThread *slot = &threadMGR->threads[threadMGR->index]; |
| |
| /* wait for parent to finish launching us before proceeding. */ |
| PR_Lock(threadMGR->threadLock); |
| PR_Unlock(threadMGR->threadLock); |
| |
| slot->rv = (*slot->startFunc)(slot->a, slot->b); |
| |
| PR_Lock(threadMGR->threadLock); |
| slot->running = rs_zombie; |
| |
| /* notify the thread exit handler. */ |
| PR_NotifyCondVar(threadMGR->threadEndQ); |
| |
| PR_Unlock(threadMGR->threadLock); |
| } |
| |
| SECStatus |
| launch_thread(GlobalThreadMgr *threadMGR, |
| startFn *startFunc, |
| void *a, |
| int b) |
| { |
| perThread *slot; |
| int i; |
| |
| if (!threadMGR->threadStartQ) { |
| threadMGR->threadLock = PR_NewLock(); |
| threadMGR->threadStartQ = PR_NewCondVar(threadMGR->threadLock); |
| threadMGR->threadEndQ = PR_NewCondVar(threadMGR->threadLock); |
| } |
| PR_Lock(threadMGR->threadLock); |
| while (threadMGR->numRunning >= MAX_THREADS) { |
| PR_WaitCondVar(threadMGR->threadStartQ, PR_INTERVAL_NO_TIMEOUT); |
| } |
| for (i = 0; i < threadMGR->numUsed; ++i) { |
| slot = &threadMGR->threads[i]; |
| if (slot->running == rs_idle) |
| break; |
| } |
| if (i >= threadMGR->numUsed) { |
| if (i >= MAX_THREADS) { |
| /* something's really wrong here. */ |
| PORT_Assert(i < MAX_THREADS); |
| PR_Unlock(threadMGR->threadLock); |
| return SECFailure; |
| } |
| ++(threadMGR->numUsed); |
| PORT_Assert(threadMGR->numUsed == i + 1); |
| slot = &threadMGR->threads[i]; |
| } |
| |
| slot->a = a; |
| slot->b = b; |
| slot->startFunc = startFunc; |
| |
| threadMGR->index = i; |
| |
| slot->prThread = PR_CreateThread(PR_USER_THREAD, |
| thread_wrapper, threadMGR, |
| PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, |
| PR_JOINABLE_THREAD, 0); |
| |
| if (slot->prThread == NULL) { |
| PR_Unlock(threadMGR->threadLock); |
| printf("Failed to launch thread!\n"); |
| return SECFailure; |
| } |
| |
| slot->inUse = 1; |
| slot->running = 1; |
| ++(threadMGR->numRunning); |
| PR_Unlock(threadMGR->threadLock); |
| |
| return SECSuccess; |
| } |
| |
| SECStatus |
| reap_threads(GlobalThreadMgr *threadMGR) |
| { |
| perThread *slot; |
| int i; |
| |
| if (!threadMGR->threadLock) |
| return SECSuccess; |
| PR_Lock(threadMGR->threadLock); |
| while (threadMGR->numRunning > 0) { |
| PR_WaitCondVar(threadMGR->threadEndQ, PR_INTERVAL_NO_TIMEOUT); |
| for (i = 0; i < threadMGR->numUsed; ++i) { |
| slot = &threadMGR->threads[i]; |
| if (slot->running == rs_zombie) { |
| /* Handle cleanup of thread here. */ |
| |
| /* Now make sure the thread has ended OK. */ |
| PR_JoinThread(slot->prThread); |
| slot->running = rs_idle; |
| --threadMGR->numRunning; |
| |
| /* notify the thread launcher. */ |
| PR_NotifyCondVar(threadMGR->threadStartQ); |
| } |
| } |
| } |
| |
| /* Safety Sam sez: make sure count is right. */ |
| for (i = 0; i < threadMGR->numUsed; ++i) { |
| slot = &threadMGR->threads[i]; |
| if (slot->running != rs_idle) { |
| fprintf(stderr, "Thread in slot %d is in state %d!\n", |
| i, slot->running); |
| } |
| } |
| PR_Unlock(threadMGR->threadLock); |
| return SECSuccess; |
| } |
| |
| void |
| destroy_thread_data(GlobalThreadMgr *threadMGR) |
| { |
| PORT_Memset(threadMGR->threads, 0, sizeof(threadMGR->threads)); |
| |
| if (threadMGR->threadEndQ) { |
| PR_DestroyCondVar(threadMGR->threadEndQ); |
| threadMGR->threadEndQ = NULL; |
| } |
| if (threadMGR->threadStartQ) { |
| PR_DestroyCondVar(threadMGR->threadStartQ); |
| threadMGR->threadStartQ = NULL; |
| } |
| if (threadMGR->threadLock) { |
| PR_DestroyLock(threadMGR->threadLock); |
| threadMGR->threadLock = NULL; |
| } |
| } |
| |
| /************************************************************************** |
| ** End thread management routines. |
| **************************************************************************/ |
| |
| void |
| lockedVars_Init(lockedVars *lv) |
| { |
| lv->count = 0; |
| lv->waiters = 0; |
| lv->lock = PR_NewLock(); |
| lv->condVar = PR_NewCondVar(lv->lock); |
| } |
| |
| void |
| lockedVars_Destroy(lockedVars *lv) |
| { |
| PR_DestroyCondVar(lv->condVar); |
| lv->condVar = NULL; |
| |
| PR_DestroyLock(lv->lock); |
| lv->lock = NULL; |
| } |
| |
| void |
| lockedVars_WaitForDone(lockedVars *lv) |
| { |
| PR_Lock(lv->lock); |
| while (lv->count > 0) { |
| PR_WaitCondVar(lv->condVar, PR_INTERVAL_NO_TIMEOUT); |
| } |
| PR_Unlock(lv->lock); |
| } |
| |
| int /* returns count */ |
| lockedVars_AddToCount(lockedVars *lv, int addend) |
| { |
| int rv; |
| |
| PR_Lock(lv->lock); |
| rv = lv->count += addend; |
| if (rv <= 0) { |
| PR_NotifyCondVar(lv->condVar); |
| } |
| PR_Unlock(lv->lock); |
| return rv; |
| } |
| |
| /* |
| * Dump cert chain in to cert.* files. This function is will |
| * create collisions while dumping cert chains if called from |
| * multiple treads. But it should not be a problem since we |
| * consider vfyserv to be single threaded(see bug 353477). |
| */ |
| |
| void |
| dumpCertChain(CERTCertificate *cert, SECCertUsage usage) |
| { |
| CERTCertificateList *certList; |
| unsigned int count = 0; |
| |
| certList = CERT_CertChainFromCert(cert, usage, PR_TRUE); |
| if (certList == NULL) { |
| errWarn("CERT_CertChainFromCert"); |
| return; |
| } |
| |
| for (count = 0; count < (unsigned int)certList->len; count++) { |
| char certFileName[16]; |
| PRFileDesc *cfd; |
| |
| PR_snprintf(certFileName, sizeof certFileName, "cert.%03d", |
| count); |
| cfd = PR_Open(certFileName, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, |
| 0664); |
| if (!cfd) { |
| PR_fprintf(PR_STDOUT, |
| "Error: couldn't save cert der in file '%s'\n", |
| certFileName); |
| } else { |
| PR_Write(cfd, certList->certs[count].data, certList->certs[count].len); |
| PR_Close(cfd); |
| PR_fprintf(PR_STDOUT, "Cert file %s was created.\n", certFileName); |
| } |
| } |
| CERT_DestroyCertificateList(certList); |
| } |