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