vfyutil.c (19554B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "vfyserv.h" 6 #include "secerr.h" 7 #include "sslerr.h" 8 #include "nspr.h" 9 #include "secutil.h" 10 11 extern PRBool dumpChain; 12 extern void dumpCertChain(CERTCertificate *, SECCertUsage); 13 14 /* Declare SSL cipher suites. */ 15 16 int ssl3CipherSuites[] = { 17 -1, /* SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA* a */ 18 -1, /* SSL_FORTEZZA_DMS_WITH_RC4_128_SHA * b */ 19 TLS_RSA_WITH_RC4_128_MD5, /* c */ 20 TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* d */ 21 TLS_RSA_WITH_DES_CBC_SHA, /* e */ 22 -1, /* TLS_RSA_EXPORT_WITH_RC4_40_MD5 * f */ 23 -1, /* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 * g */ 24 -1, /* SSL_FORTEZZA_DMS_WITH_NULL_SHA * h */ 25 TLS_RSA_WITH_NULL_MD5, /* i */ 26 -1, /* SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA * j */ 27 -1, /* SSL_RSA_FIPS_WITH_DES_CBC_SHA * k */ 28 -1, /* TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA * l */ 29 -1, /* TLS_RSA_EXPORT1024_WITH_RC4_56_SHA * m */ 30 TLS_RSA_WITH_RC4_128_SHA, /* n */ 31 TLS_DHE_DSS_WITH_RC4_128_SHA, /* o */ 32 TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, /* p */ 33 TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, /* q */ 34 TLS_DHE_RSA_WITH_DES_CBC_SHA, /* r */ 35 TLS_DHE_DSS_WITH_DES_CBC_SHA, /* s */ 36 TLS_DHE_DSS_WITH_AES_128_CBC_SHA, /* t */ 37 TLS_DHE_RSA_WITH_AES_128_CBC_SHA, /* u */ 38 TLS_RSA_WITH_AES_128_CBC_SHA, /* v */ 39 TLS_DHE_DSS_WITH_AES_256_CBC_SHA, /* w */ 40 TLS_DHE_RSA_WITH_AES_256_CBC_SHA, /* x */ 41 TLS_RSA_WITH_AES_256_CBC_SHA, /* y */ 42 TLS_RSA_WITH_NULL_SHA, /* z */ 43 0 44 }; 45 int numSSL3CipherSuites = PR_ARRAY_SIZE(ssl3CipherSuites); 46 47 /************************************************************************** 48 ** 49 ** SSL callback routines. 50 ** 51 **************************************************************************/ 52 53 /* Function: char * myPasswd() 54 * 55 * Purpose: This function is our custom password handler that is called by 56 * SSL when retreiving private certs and keys from the database. Returns a 57 * pointer to a string that with a password for the database. Password pointer 58 * should point to dynamically allocated memory that will be freed later. 59 */ 60 char * 61 myPasswd(PK11SlotInfo *info, PRBool retry, void *arg) 62 { 63 char *passwd = NULL; 64 65 if ((!retry) && arg) { 66 passwd = PORT_Strdup((char *)arg); 67 } 68 return passwd; 69 } 70 71 /* Function: SECStatus myAuthCertificate() 72 * 73 * Purpose: This function is our custom certificate authentication handler. 74 * 75 * Note: This implementation is essentially the same as the default 76 * SSL_AuthCertificate(). 77 */ 78 SECStatus 79 myAuthCertificate(void *arg, PRFileDesc *socket, 80 PRBool checksig, PRBool isServer) 81 { 82 83 SECCertificateUsage certUsage; 84 CERTCertList *peerChain; 85 CERTCertificate *cert; 86 void *pinArg; 87 char *hostName; 88 SECStatus secStatus; 89 90 if (!arg || !socket) { 91 errWarn("myAuthCertificate"); 92 return SECFailure; 93 } 94 95 /* Define how the cert is being used based upon the isServer flag. */ 96 97 certUsage = isServer ? certificateUsageSSLClient : certificateUsageSSLServer; 98 99 /* Construct, or obtain references to, CERTCertificates for the chain 100 * presented by the peer. This ensures that the certificates are in 101 * the certificate DB during CERT_VerifyCertificateNow. 102 */ 103 peerChain = SSL_PeerCertificateChain(socket); 104 if (!peerChain) { 105 return SECFailure; 106 } 107 108 if (CERT_LIST_EMPTY(peerChain) || !CERT_LIST_HEAD(peerChain)->cert) { 109 CERT_DestroyCertList(peerChain); 110 return SECFailure; 111 } 112 cert = CERT_LIST_HEAD(peerChain)->cert; 113 114 pinArg = SSL_RevealPinArg(socket); 115 116 if (dumpChain == PR_TRUE) { 117 dumpCertChain(cert, certUsage); 118 } 119 120 secStatus = CERT_VerifyCertificateNow((CERTCertDBHandle *)arg, 121 cert, 122 checksig, 123 certUsage, 124 pinArg, 125 NULL); 126 127 /* If this is a server, we're finished. */ 128 if (isServer || secStatus != SECSuccess) { 129 SECU_printCertProblems(stderr, (CERTCertDBHandle *)arg, cert, 130 checksig, certUsage, pinArg, PR_FALSE); 131 CERT_DestroyCertList(peerChain); 132 return secStatus; 133 } 134 135 /* Certificate is OK. Since this is the client side of an SSL 136 * connection, we need to verify that the name field in the cert 137 * matches the desired hostname. This is our defense against 138 * man-in-the-middle attacks. 139 */ 140 141 /* SSL_RevealURL returns a hostName, not an URL. */ 142 hostName = SSL_RevealURL(socket); 143 144 if (hostName && hostName[0]) { 145 secStatus = CERT_VerifyCertName(cert, hostName); 146 } else { 147 PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0); 148 secStatus = SECFailure; 149 } 150 151 if (hostName) 152 PR_Free(hostName); 153 154 CERT_DestroyCertList(peerChain); 155 return secStatus; 156 } 157 158 /* Function: SECStatus myBadCertHandler() 159 * 160 * Purpose: This callback is called when the incoming certificate is not 161 * valid. We define a certain set of parameters that still cause the 162 * certificate to be "valid" for this session, and return SECSuccess to cause 163 * the server to continue processing the request when any of these conditions 164 * are met. Otherwise, SECFailure is return and the server rejects the 165 * request. 166 */ 167 SECStatus 168 myBadCertHandler(void *arg, PRFileDesc *socket) 169 { 170 171 SECStatus secStatus = SECFailure; 172 PRErrorCode err; 173 174 /* log invalid cert here */ 175 176 if (!arg) { 177 return secStatus; 178 } 179 180 *(PRErrorCode *)arg = err = PORT_GetError(); 181 182 /* If any of the cases in the switch are met, then we will proceed */ 183 /* with the processing of the request anyway. Otherwise, the default */ 184 /* case will be reached and we will reject the request. */ 185 186 switch (err) { 187 case SEC_ERROR_INVALID_AVA: 188 case SEC_ERROR_INVALID_TIME: 189 case SEC_ERROR_BAD_SIGNATURE: 190 case SEC_ERROR_EXPIRED_CERTIFICATE: 191 case SEC_ERROR_UNKNOWN_ISSUER: 192 case SEC_ERROR_UNTRUSTED_CERT: 193 case SEC_ERROR_CERT_VALID: 194 case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: 195 case SEC_ERROR_CRL_EXPIRED: 196 case SEC_ERROR_CRL_BAD_SIGNATURE: 197 case SEC_ERROR_EXTENSION_VALUE_INVALID: 198 case SEC_ERROR_CA_CERT_INVALID: 199 case SEC_ERROR_CERT_USAGES_INVALID: 200 case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: 201 secStatus = SECSuccess; 202 break; 203 default: 204 secStatus = SECFailure; 205 break; 206 } 207 208 fprintf(stderr, "Bad certificate: %d, %s\n", err, SECU_Strerror(err)); 209 210 return secStatus; 211 } 212 213 /* Function: SECStatus ownGetClientAuthData() 214 * 215 * Purpose: This callback is used by SSL to pull client certificate 216 * information upon server request. 217 */ 218 SECStatus 219 myGetClientAuthData(void *arg, 220 PRFileDesc *socket, 221 struct CERTDistNamesStr *caNames, 222 struct CERTCertificateStr **pRetCert, 223 struct SECKEYPrivateKeyStr **pRetKey) 224 { 225 226 CERTCertificate *cert; 227 SECKEYPrivateKey *privKey; 228 char *chosenNickName = (char *)arg; 229 void *proto_win = NULL; 230 SECStatus secStatus = SECFailure; 231 232 proto_win = SSL_RevealPinArg(socket); 233 234 if (chosenNickName) { 235 cert = PK11_FindCertFromNickname(chosenNickName, proto_win); 236 if (cert) { 237 privKey = PK11_FindKeyByAnyCert(cert, proto_win); 238 if (privKey) { 239 secStatus = SECSuccess; 240 } else { 241 CERT_DestroyCertificate(cert); 242 } 243 } 244 } else { /* no nickname given, automatically find the right cert */ 245 CERTCertNicknames *names; 246 int i; 247 248 names = CERT_GetCertNicknames(CERT_GetDefaultCertDB(), 249 SEC_CERT_NICKNAMES_USER, proto_win); 250 251 if (names != NULL) { 252 for (i = 0; i < names->numnicknames; i++) { 253 254 cert = PK11_FindCertFromNickname(names->nicknames[i], 255 proto_win); 256 if (!cert) { 257 continue; 258 } 259 260 /* Only check unexpired certs */ 261 if (CERT_CheckCertValidTimes(cert, PR_Now(), PR_FALSE) != 262 secCertTimeValid) { 263 CERT_DestroyCertificate(cert); 264 continue; 265 } 266 267 secStatus = NSS_CmpCertChainWCANames(cert, caNames); 268 if (secStatus == SECSuccess) { 269 privKey = PK11_FindKeyByAnyCert(cert, proto_win); 270 if (privKey) { 271 break; 272 } 273 secStatus = SECFailure; 274 } 275 CERT_DestroyCertificate(cert); 276 } /* for loop */ 277 CERT_FreeNicknames(names); 278 } 279 } 280 281 if (secStatus == SECSuccess) { 282 *pRetCert = cert; 283 *pRetKey = privKey; 284 } 285 286 return secStatus; 287 } 288 289 /* Function: void myHandshakeCallback() 290 * 291 * Purpose: Called by SSL to inform application that the handshake is 292 * complete. This function is mostly used on the server side of an SSL 293 * connection, although it is provided for a client as well. 294 * Useful when a non-blocking SSL_ReHandshake or SSL_ResetHandshake 295 * is used to initiate a handshake. 296 * 297 * A typical scenario would be: 298 * 299 * 1. Server accepts an SSL connection from the client without client auth. 300 * 2. Client sends a request. 301 * 3. Server determines that to service request it needs to authenticate the 302 * client and initiates another handshake requesting client auth. 303 * 4. While handshake is in progress, server can do other work or spin waiting 304 * for the handshake to complete. 305 * 5. Server is notified that handshake has been successfully completed by 306 * the custom handshake callback function and it can service the client's 307 * request. 308 * 309 * Note: This function is not implemented in this sample, as we are using 310 * blocking sockets. 311 */ 312 void 313 myHandshakeCallback(PRFileDesc *socket, void *arg) 314 { 315 fprintf(stderr, "Handshake Complete: SERVER CONFIGURED CORRECTLY\n"); 316 } 317 318 /************************************************************************** 319 ** 320 ** Routines for disabling SSL ciphers. 321 ** 322 **************************************************************************/ 323 324 void 325 disableAllSSLCiphers(void) 326 { 327 const PRUint16 *allSuites = SSL_ImplementedCiphers; 328 int i = SSL_NumImplementedCiphers; 329 SECStatus rv; 330 331 /* disable all the SSL3 cipher suites */ 332 while (--i >= 0) { 333 PRUint16 suite = allSuites[i]; 334 rv = SSL_CipherPrefSetDefault(suite, PR_FALSE); 335 if (rv != SECSuccess) { 336 fprintf(stderr, 337 "SSL_CipherPrefSetDefault didn't like value 0x%04x (i = %d)\n", 338 suite, i); 339 errWarn("SSL_CipherPrefSetDefault"); 340 exit(2); 341 } 342 } 343 } 344 345 /************************************************************************** 346 ** 347 ** Error and information routines. 348 ** 349 **************************************************************************/ 350 351 void 352 errWarn(char *function) 353 { 354 PRErrorCode errorNumber = PR_GetError(); 355 const char *errorString = SECU_Strerror(errorNumber); 356 357 fprintf(stderr, "Error in function %s: %d\n - %s\n", 358 function, errorNumber, errorString); 359 } 360 361 void 362 exitErr(char *function) 363 { 364 errWarn(function); 365 /* Exit gracefully. */ 366 /* ignoring return value of NSS_Shutdown as code exits with 1 anyway*/ 367 (void)NSS_Shutdown(); 368 PR_Cleanup(); 369 exit(1); 370 } 371 372 void 373 printSecurityInfo(FILE *outfile, PRFileDesc *fd) 374 { 375 char *cp; /* bulk cipher name */ 376 char *ip; /* cert issuer DN */ 377 char *sp; /* cert subject DN */ 378 int op; /* High, Low, Off */ 379 int kp0; /* total key bits */ 380 int kp1; /* secret key bits */ 381 int result; 382 SSL3Statistics *ssl3stats = SSL_GetStatistics(); 383 384 if (!outfile) { 385 outfile = stdout; 386 } 387 388 result = SSL_SecurityStatus(fd, &op, &cp, &kp0, &kp1, &ip, &sp); 389 if (result != SECSuccess) 390 return; 391 fprintf(outfile, 392 " bulk cipher %s, %d secret key bits, %d key bits, status: %d\n" 393 " subject DN:\n %s\n" 394 " issuer DN:\n %s\n", 395 cp, kp1, kp0, op, sp, ip); 396 PR_Free(cp); 397 PR_Free(ip); 398 PR_Free(sp); 399 400 fprintf(outfile, 401 " %ld cache hits; %ld cache misses, %ld cache not reusable\n", 402 ssl3stats->hch_sid_cache_hits, ssl3stats->hch_sid_cache_misses, 403 ssl3stats->hch_sid_cache_not_ok); 404 } 405 406 /************************************************************************** 407 ** Begin thread management routines and data. 408 **************************************************************************/ 409 410 void 411 thread_wrapper(void *arg) 412 { 413 GlobalThreadMgr *threadMGR = (GlobalThreadMgr *)arg; 414 perThread *slot = &threadMGR->threads[threadMGR->index]; 415 416 /* wait for parent to finish launching us before proceeding. */ 417 PR_Lock(threadMGR->threadLock); 418 PR_Unlock(threadMGR->threadLock); 419 420 slot->rv = (*slot->startFunc)(slot->a, slot->b); 421 422 PR_Lock(threadMGR->threadLock); 423 slot->running = rs_zombie; 424 425 /* notify the thread exit handler. */ 426 PR_NotifyCondVar(threadMGR->threadEndQ); 427 428 PR_Unlock(threadMGR->threadLock); 429 } 430 431 SECStatus 432 launch_thread(GlobalThreadMgr *threadMGR, 433 startFn *startFunc, 434 void *a, 435 int b) 436 { 437 perThread *slot; 438 int i; 439 440 if (!threadMGR->threadStartQ) { 441 threadMGR->threadLock = PR_NewLock(); 442 threadMGR->threadStartQ = PR_NewCondVar(threadMGR->threadLock); 443 threadMGR->threadEndQ = PR_NewCondVar(threadMGR->threadLock); 444 } 445 PR_Lock(threadMGR->threadLock); 446 while (threadMGR->numRunning >= MAX_THREADS) { 447 PR_WaitCondVar(threadMGR->threadStartQ, PR_INTERVAL_NO_TIMEOUT); 448 } 449 for (i = 0; i < threadMGR->numUsed; ++i) { 450 slot = &threadMGR->threads[i]; 451 if (slot->running == rs_idle) 452 break; 453 } 454 if (i >= threadMGR->numUsed) { 455 if (i >= MAX_THREADS) { 456 /* something's really wrong here. */ 457 PORT_Assert(i < MAX_THREADS); 458 PR_Unlock(threadMGR->threadLock); 459 return SECFailure; 460 } 461 ++(threadMGR->numUsed); 462 PORT_Assert(threadMGR->numUsed == i + 1); 463 slot = &threadMGR->threads[i]; 464 } 465 466 slot->a = a; 467 slot->b = b; 468 slot->startFunc = startFunc; 469 470 threadMGR->index = i; 471 472 slot->prThread = PR_CreateThread(PR_USER_THREAD, 473 thread_wrapper, threadMGR, 474 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, 475 PR_JOINABLE_THREAD, 0); 476 477 if (slot->prThread == NULL) { 478 PR_Unlock(threadMGR->threadLock); 479 printf("Failed to launch thread!\n"); 480 return SECFailure; 481 } 482 483 slot->inUse = 1; 484 slot->running = 1; 485 ++(threadMGR->numRunning); 486 PR_Unlock(threadMGR->threadLock); 487 488 return SECSuccess; 489 } 490 491 SECStatus 492 reap_threads(GlobalThreadMgr *threadMGR) 493 { 494 perThread *slot; 495 int i; 496 497 if (!threadMGR->threadLock) 498 return SECSuccess; 499 PR_Lock(threadMGR->threadLock); 500 while (threadMGR->numRunning > 0) { 501 PR_WaitCondVar(threadMGR->threadEndQ, PR_INTERVAL_NO_TIMEOUT); 502 for (i = 0; i < threadMGR->numUsed; ++i) { 503 slot = &threadMGR->threads[i]; 504 if (slot->running == rs_zombie) { 505 /* Handle cleanup of thread here. */ 506 507 /* Now make sure the thread has ended OK. */ 508 PR_JoinThread(slot->prThread); 509 slot->running = rs_idle; 510 --threadMGR->numRunning; 511 512 /* notify the thread launcher. */ 513 PR_NotifyCondVar(threadMGR->threadStartQ); 514 } 515 } 516 } 517 518 /* Safety Sam sez: make sure count is right. */ 519 for (i = 0; i < threadMGR->numUsed; ++i) { 520 slot = &threadMGR->threads[i]; 521 if (slot->running != rs_idle) { 522 fprintf(stderr, "Thread in slot %d is in state %d!\n", 523 i, slot->running); 524 } 525 } 526 PR_Unlock(threadMGR->threadLock); 527 return SECSuccess; 528 } 529 530 void 531 destroy_thread_data(GlobalThreadMgr *threadMGR) 532 { 533 PORT_Memset(threadMGR->threads, 0, sizeof(threadMGR->threads)); 534 535 if (threadMGR->threadEndQ) { 536 PR_DestroyCondVar(threadMGR->threadEndQ); 537 threadMGR->threadEndQ = NULL; 538 } 539 if (threadMGR->threadStartQ) { 540 PR_DestroyCondVar(threadMGR->threadStartQ); 541 threadMGR->threadStartQ = NULL; 542 } 543 if (threadMGR->threadLock) { 544 PR_DestroyLock(threadMGR->threadLock); 545 threadMGR->threadLock = NULL; 546 } 547 } 548 549 /************************************************************************** 550 ** End thread management routines. 551 **************************************************************************/ 552 553 void 554 lockedVars_Init(lockedVars *lv) 555 { 556 lv->count = 0; 557 lv->waiters = 0; 558 lv->lock = PR_NewLock(); 559 lv->condVar = PR_NewCondVar(lv->lock); 560 } 561 562 void 563 lockedVars_Destroy(lockedVars *lv) 564 { 565 PR_DestroyCondVar(lv->condVar); 566 lv->condVar = NULL; 567 568 PR_DestroyLock(lv->lock); 569 lv->lock = NULL; 570 } 571 572 void 573 lockedVars_WaitForDone(lockedVars *lv) 574 { 575 PR_Lock(lv->lock); 576 while (lv->count > 0) { 577 PR_WaitCondVar(lv->condVar, PR_INTERVAL_NO_TIMEOUT); 578 } 579 PR_Unlock(lv->lock); 580 } 581 582 int /* returns count */ 583 lockedVars_AddToCount(lockedVars *lv, int addend) 584 { 585 int rv; 586 587 PR_Lock(lv->lock); 588 rv = lv->count += addend; 589 if (rv <= 0) { 590 PR_NotifyCondVar(lv->condVar); 591 } 592 PR_Unlock(lv->lock); 593 return rv; 594 } 595 596 /* 597 * Dump cert chain in to cert.* files. This function is will 598 * create collisions while dumping cert chains if called from 599 * multiple treads. But it should not be a problem since we 600 * consider vfyserv to be single threaded(see bug 353477). 601 */ 602 603 void 604 dumpCertChain(CERTCertificate *cert, SECCertUsage usage) 605 { 606 CERTCertificateList *certList; 607 unsigned int count = 0; 608 609 certList = CERT_CertChainFromCert(cert, usage, PR_TRUE); 610 if (certList == NULL) { 611 errWarn("CERT_CertChainFromCert"); 612 return; 613 } 614 615 for (count = 0; count < (unsigned int)certList->len; count++) { 616 char certFileName[16]; 617 PRFileDesc *cfd; 618 619 PR_snprintf(certFileName, sizeof certFileName, "cert.%03d", 620 count); 621 cfd = PR_Open(certFileName, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 622 0664); 623 if (!cfd) { 624 PR_fprintf(PR_STDOUT, 625 "Error: couldn't save cert der in file '%s'\n", 626 certFileName); 627 } else { 628 PR_Write(cfd, certList->certs[count].data, certList->certs[count].len); 629 PR_Close(cfd); 630 PR_fprintf(PR_STDOUT, "Cert file %s was created.\n", certFileName); 631 } 632 } 633 CERT_DestroyCertificateList(certList); 634 }