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