gecko-dev/security/nss/cmd/vfyserv/vfyutil.c
2008-06-06 08:40:11 -04:00

666 lines
19 KiB
C

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Netscape security libraries.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1994-2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#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 ssl2CipherSuites[] = {
SSL_EN_RC4_128_WITH_MD5, /* A */
SSL_EN_RC4_128_EXPORT40_WITH_MD5, /* B */
SSL_EN_RC2_128_CBC_WITH_MD5, /* C */
SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5, /* D */
SSL_EN_DES_64_CBC_WITH_MD5, /* E */
SSL_EN_DES_192_EDE3_CBC_WITH_MD5, /* F */
0
};
int ssl3CipherSuites[] = {
-1, /* SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA* a */
-1, /* SSL_FORTEZZA_DMS_WITH_RC4_128_SHA, * b */
SSL_RSA_WITH_RC4_128_MD5, /* c */
SSL_RSA_WITH_3DES_EDE_CBC_SHA, /* d */
SSL_RSA_WITH_DES_CBC_SHA, /* e */
SSL_RSA_EXPORT_WITH_RC4_40_MD5, /* f */
SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5, /* g */
-1, /* SSL_FORTEZZA_DMS_WITH_NULL_SHA, * h */
SSL_RSA_WITH_NULL_MD5, /* i */
SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA, /* j */
SSL_RSA_FIPS_WITH_DES_CBC_SHA, /* k */
TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, /* l */
TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, /* m */
SSL_RSA_WITH_RC4_128_SHA, /* n */
TLS_DHE_DSS_WITH_RC4_128_SHA, /* o */
SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, /* p */
SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, /* q */
SSL_DHE_RSA_WITH_DES_CBC_SHA, /* r */
SSL_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 */
SSL_RSA_WITH_NULL_SHA, /* z */
0
};
/**************************************************************************
**
** 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 *cipherSuites = SSL_ImplementedCiphers;
int i = SSL_NumImplementedCiphers;
SECStatus rv;
/* disable all the SSL3 cipher suites */
while (--i >= 0) {
PRUint16 suite = cipherSuites[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;
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);
}