SSL: reworked ngx_ssl_certificate().
This makes it possible to reuse certificate loading at runtime,
as introduced in the following patches.
Additionally, this improves error logging, so nginx will now log
human-friendly messages "cannot load certificate" instead of only
referring to sometimes cryptic names of OpenSSL functions.
Maxim Dounin
3 years ago
17 | 17 | } ngx_openssl_conf_t; |
18 | 18 | |
19 | 19 | |
20 | static X509 *ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, | |
21 | ngx_str_t *cert, STACK_OF(X509) **chain); | |
22 | static EVP_PKEY *ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, | |
23 | ngx_str_t *key, ngx_array_t *passwords); | |
20 | 24 | static int ngx_ssl_password_callback(char *buf, int size, int rwflag, |
21 | 25 | void *userdata); |
22 | 26 | static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store); |
406 | 410 | ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, |
407 | 411 | ngx_str_t *key, ngx_array_t *passwords) |
408 | 412 | { |
409 | BIO *bio; | |
410 | X509 *x509; | |
411 | u_long n; | |
412 | ngx_str_t *pwd; | |
413 | ngx_uint_t tries; | |
414 | ||
415 | if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { | |
416 | return NGX_ERROR; | |
417 | } | |
418 | ||
419 | /* | |
420 | * we can't use SSL_CTX_use_certificate_chain_file() as it doesn't | |
421 | * allow to access certificate later from SSL_CTX, so we reimplement | |
422 | * it here | |
423 | */ | |
424 | ||
425 | bio = BIO_new_file((char *) cert->data, "r"); | |
426 | if (bio == NULL) { | |
427 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
428 | "BIO_new_file(\"%s\") failed", cert->data); | |
429 | return NGX_ERROR; | |
430 | } | |
431 | ||
432 | x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); | |
413 | char *err; | |
414 | X509 *x509; | |
415 | EVP_PKEY *pkey; | |
416 | STACK_OF(X509) *chain; | |
417 | ||
418 | x509 = ngx_ssl_load_certificate(cf->pool, &err, cert, &chain); | |
433 | 419 | if (x509 == NULL) { |
434 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
435 | "PEM_read_bio_X509_AUX(\"%s\") failed", cert->data); | |
436 | BIO_free(bio); | |
420 | if (err != NULL) { | |
421 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
422 | "cannot load certificate \"%s\": %s", | |
423 | cert->data, err); | |
424 | } | |
425 | ||
437 | 426 | return NGX_ERROR; |
438 | 427 | } |
439 | 428 | |
441 | 430 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, |
442 | 431 | "SSL_CTX_use_certificate(\"%s\") failed", cert->data); |
443 | 432 | X509_free(x509); |
444 | BIO_free(bio); | |
433 | sk_X509_pop_free(chain, X509_free); | |
445 | 434 | return NGX_ERROR; |
446 | 435 | } |
447 | 436 | |
450 | 439 | { |
451 | 440 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); |
452 | 441 | X509_free(x509); |
453 | BIO_free(bio); | |
442 | sk_X509_pop_free(chain, X509_free); | |
454 | 443 | return NGX_ERROR; |
455 | 444 | } |
456 | 445 | |
460 | 449 | { |
461 | 450 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); |
462 | 451 | X509_free(x509); |
463 | BIO_free(bio); | |
464 | return NGX_ERROR; | |
465 | } | |
466 | ||
467 | if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, x509) | |
468 | == 0) | |
469 | { | |
452 | sk_X509_pop_free(chain, X509_free); | |
453 | return NGX_ERROR; | |
454 | } | |
455 | ||
456 | if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, x509) == 0) { | |
470 | 457 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, |
471 | 458 | "SSL_CTX_set_ex_data() failed"); |
472 | 459 | X509_free(x509); |
460 | sk_X509_pop_free(chain, X509_free); | |
461 | return NGX_ERROR; | |
462 | } | |
463 | ||
464 | /* | |
465 | * Note that x509 is not freed here, but will be instead freed in | |
466 | * ngx_ssl_cleanup_ctx(). This is because we need to preserve all | |
467 | * certificates to be able to iterate all of them through exdata | |
468 | * (ngx_ssl_certificate_index, ngx_ssl_next_certificate_index), | |
469 | * while OpenSSL can free a certificate if it is replaced with another | |
470 | * certificate of the same type. | |
471 | */ | |
472 | ||
473 | #ifdef SSL_CTX_set0_chain | |
474 | ||
475 | if (SSL_CTX_set0_chain(ssl->ctx, chain) == 0) { | |
476 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
477 | "SSL_CTX_set0_chain(\"%s\") failed", cert->data); | |
478 | sk_X509_pop_free(chain, X509_free); | |
479 | return NGX_ERROR; | |
480 | } | |
481 | ||
482 | #else | |
483 | { | |
484 | int n; | |
485 | ||
486 | /* SSL_CTX_set0_chain() is only available in OpenSSL 1.0.2+ */ | |
487 | ||
488 | n = sk_X509_num(chain); | |
489 | ||
490 | while (n--) { | |
491 | x509 = sk_X509_shift(chain); | |
492 | ||
493 | if (SSL_CTX_add_extra_chain_cert(ssl->ctx, x509) == 0) { | |
494 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
495 | "SSL_CTX_add_extra_chain_cert(\"%s\") failed", | |
496 | cert->data); | |
497 | sk_X509_pop_free(chain, X509_free); | |
498 | return NGX_ERROR; | |
499 | } | |
500 | } | |
501 | ||
502 | sk_X509_free(chain); | |
503 | } | |
504 | #endif | |
505 | ||
506 | pkey = ngx_ssl_load_certificate_key(cf->pool, &err, key, passwords); | |
507 | if (pkey == NULL) { | |
508 | if (err != NULL) { | |
509 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
510 | "cannot load certificate key \"%s\": %s", | |
511 | key->data, err); | |
512 | } | |
513 | ||
514 | return NGX_ERROR; | |
515 | } | |
516 | ||
517 | if (SSL_CTX_use_PrivateKey(ssl->ctx, pkey) == 0) { | |
518 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
519 | "SSL_CTX_use_PrivateKey(\"%s\") failed", key->data); | |
520 | EVP_PKEY_free(pkey); | |
521 | return NGX_ERROR; | |
522 | } | |
523 | ||
524 | EVP_PKEY_free(pkey); | |
525 | ||
526 | return NGX_OK; | |
527 | } | |
528 | ||
529 | ||
530 | static X509 * | |
531 | ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, ngx_str_t *cert, | |
532 | STACK_OF(X509) **chain) | |
533 | { | |
534 | BIO *bio; | |
535 | X509 *x509, *temp; | |
536 | u_long n; | |
537 | ||
538 | if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, cert) | |
539 | != NGX_OK) | |
540 | { | |
541 | *err = NULL; | |
542 | return NULL; | |
543 | } | |
544 | ||
545 | /* | |
546 | * we can't use SSL_CTX_use_certificate_chain_file() as it doesn't | |
547 | * allow to access certificate later from SSL_CTX, so we reimplement | |
548 | * it here | |
549 | */ | |
550 | ||
551 | bio = BIO_new_file((char *) cert->data, "r"); | |
552 | if (bio == NULL) { | |
553 | *err = "BIO_new_file() failed"; | |
554 | return NULL; | |
555 | } | |
556 | ||
557 | /* certificate itself */ | |
558 | ||
559 | x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); | |
560 | if (x509 == NULL) { | |
561 | *err = "PEM_read_bio_X509_AUX() failed"; | |
473 | 562 | BIO_free(bio); |
474 | return NGX_ERROR; | |
475 | } | |
476 | ||
477 | /* read rest of the chain */ | |
563 | return NULL; | |
564 | } | |
565 | ||
566 | /* rest of the chain */ | |
567 | ||
568 | *chain = sk_X509_new_null(); | |
569 | if (*chain == NULL) { | |
570 | *err = "sk_X509_new_null() failed"; | |
571 | BIO_free(bio); | |
572 | X509_free(x509); | |
573 | return NULL; | |
574 | } | |
478 | 575 | |
479 | 576 | for ( ;; ) { |
480 | 577 | |
481 | x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); | |
482 | if (x509 == NULL) { | |
578 | temp = PEM_read_bio_X509(bio, NULL, NULL, NULL); | |
579 | if (temp == NULL) { | |
483 | 580 | n = ERR_peek_last_error(); |
484 | 581 | |
485 | 582 | if (ERR_GET_LIB(n) == ERR_LIB_PEM |
492 | 589 | |
493 | 590 | /* some real error */ |
494 | 591 | |
495 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
496 | "PEM_read_bio_X509(\"%s\") failed", cert->data); | |
592 | *err = "PEM_read_bio_X509() failed"; | |
497 | 593 | BIO_free(bio); |
498 | return NGX_ERROR; | |
499 | } | |
500 | ||
501 | #ifdef SSL_CTRL_CHAIN_CERT | |
502 | ||
503 | /* | |
504 | * SSL_CTX_add0_chain_cert() is needed to add chain to | |
505 | * a particular certificate when multiple certificates are used; | |
506 | * only available in OpenSSL 1.0.2+ | |
507 | */ | |
508 | ||
509 | if (SSL_CTX_add0_chain_cert(ssl->ctx, x509) == 0) { | |
510 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
511 | "SSL_CTX_add0_chain_cert(\"%s\") failed", | |
512 | cert->data); | |
513 | 594 | X509_free(x509); |
595 | sk_X509_pop_free(*chain, X509_free); | |
596 | return NULL; | |
597 | } | |
598 | ||
599 | if (sk_X509_push(*chain, temp) == 0) { | |
600 | *err = "sk_X509_push() failed"; | |
514 | 601 | BIO_free(bio); |
515 | return NGX_ERROR; | |
516 | } | |
517 | ||
518 | #else | |
519 | if (SSL_CTX_add_extra_chain_cert(ssl->ctx, x509) == 0) { | |
520 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
521 | "SSL_CTX_add_extra_chain_cert(\"%s\") failed", | |
522 | cert->data); | |
523 | 602 | X509_free(x509); |
524 | BIO_free(bio); | |
525 | return NGX_ERROR; | |
526 | } | |
527 | #endif | |
603 | sk_X509_pop_free(*chain, X509_free); | |
604 | return NULL; | |
605 | } | |
528 | 606 | } |
529 | 607 | |
530 | 608 | BIO_free(bio); |
609 | ||
610 | return x509; | |
611 | } | |
612 | ||
613 | ||
614 | static EVP_PKEY * | |
615 | ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, | |
616 | ngx_str_t *key, ngx_array_t *passwords) | |
617 | { | |
618 | BIO *bio; | |
619 | EVP_PKEY *pkey; | |
620 | ngx_str_t *pwd; | |
621 | ngx_uint_t tries; | |
622 | pem_password_cb *cb; | |
531 | 623 | |
532 | 624 | if (ngx_strncmp(key->data, "engine:", sizeof("engine:") - 1) == 0) { |
533 | 625 | |
541 | 633 | last = (u_char *) ngx_strchr(p, ':'); |
542 | 634 | |
543 | 635 | if (last == NULL) { |
544 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | |
545 | "invalid syntax in \"%V\"", key); | |
546 | return NGX_ERROR; | |
636 | *err = "invalid syntax"; | |
637 | return NULL; | |
547 | 638 | } |
548 | 639 | |
549 | 640 | *last = '\0'; |
551 | 642 | engine = ENGINE_by_id((char *) p); |
552 | 643 | |
553 | 644 | if (engine == NULL) { |
554 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
555 | "ENGINE_by_id(\"%s\") failed", p); | |
556 | return NGX_ERROR; | |
645 | *err = "ENGINE_by_id() failed"; | |
646 | return NULL; | |
557 | 647 | } |
558 | 648 | |
559 | 649 | *last++ = ':'; |
561 | 651 | pkey = ENGINE_load_private_key(engine, (char *) last, 0, 0); |
562 | 652 | |
563 | 653 | if (pkey == NULL) { |
564 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
565 | "ENGINE_load_private_key(\"%s\") failed", last); | |
654 | *err = "ENGINE_load_private_key() failed"; | |
566 | 655 | ENGINE_free(engine); |
567 | return NGX_ERROR; | |
656 | return NULL; | |
568 | 657 | } |
569 | 658 | |
570 | 659 | ENGINE_free(engine); |
571 | 660 | |
572 | if (SSL_CTX_use_PrivateKey(ssl->ctx, pkey) == 0) { | |
573 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
574 | "SSL_CTX_use_PrivateKey(\"%s\") failed", last); | |
575 | EVP_PKEY_free(pkey); | |
576 | return NGX_ERROR; | |
577 | } | |
578 | ||
579 | EVP_PKEY_free(pkey); | |
580 | ||
581 | return NGX_OK; | |
661 | return pkey; | |
582 | 662 | |
583 | 663 | #else |
584 | 664 | |
585 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | |
586 | "loading \"engine:...\" certificate keys " | |
587 | "is not supported"); | |
588 | return NGX_ERROR; | |
589 | ||
590 | #endif | |
591 | } | |
592 | ||
593 | if (ngx_conf_full_name(cf->cycle, key, 1) != NGX_OK) { | |
594 | return NGX_ERROR; | |
665 | *err = "loading \"engine:...\" certificate keys is not supported"; | |
666 | return NULL; | |
667 | ||
668 | #endif | |
669 | } | |
670 | ||
671 | if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, key) | |
672 | != NGX_OK) | |
673 | { | |
674 | *err = NULL; | |
675 | return NULL; | |
676 | } | |
677 | ||
678 | bio = BIO_new_file((char *) key->data, "r"); | |
679 | if (bio == NULL) { | |
680 | *err = "BIO_new_file() failed"; | |
681 | return NULL; | |
595 | 682 | } |
596 | 683 | |
597 | 684 | if (passwords) { |
598 | 685 | tries = passwords->nelts; |
599 | 686 | pwd = passwords->elts; |
600 | ||
601 | SSL_CTX_set_default_passwd_cb(ssl->ctx, ngx_ssl_password_callback); | |
602 | SSL_CTX_set_default_passwd_cb_userdata(ssl->ctx, pwd); | |
687 | cb = ngx_ssl_password_callback; | |
603 | 688 | |
604 | 689 | } else { |
605 | 690 | tries = 1; |
606 | #if (NGX_SUPPRESS_WARN) | |
607 | 691 | pwd = NULL; |
608 | #endif | |
692 | cb = NULL; | |
609 | 693 | } |
610 | 694 | |
611 | 695 | for ( ;; ) { |
612 | 696 | |
613 | if (SSL_CTX_use_PrivateKey_file(ssl->ctx, (char *) key->data, | |
614 | SSL_FILETYPE_PEM) | |
615 | != 0) | |
616 | { | |
697 | pkey = PEM_read_bio_PrivateKey(bio, NULL, cb, pwd); | |
698 | if (pkey != NULL) { | |
617 | 699 | break; |
618 | 700 | } |
619 | 701 | |
620 | 702 | if (--tries) { |
621 | 703 | ERR_clear_error(); |
622 | SSL_CTX_set_default_passwd_cb_userdata(ssl->ctx, ++pwd); | |
704 | (void) BIO_reset(bio); | |
705 | pwd++; | |
623 | 706 | continue; |
624 | 707 | } |
625 | 708 | |
626 | ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, | |
627 | "SSL_CTX_use_PrivateKey_file(\"%s\") failed", key->data); | |
628 | return NGX_ERROR; | |
629 | } | |
630 | ||
631 | SSL_CTX_set_default_passwd_cb(ssl->ctx, NULL); | |
632 | ||
633 | return NGX_OK; | |
709 | *err = "PEM_read_bio_PrivateKey() failed"; | |
710 | BIO_free(bio); | |
711 | return NULL; | |
712 | } | |
713 | ||
714 | BIO_free(bio); | |
715 | ||
716 | return pkey; | |
634 | 717 | } |
635 | 718 | |
636 | 719 |