[PATCH 6 of 6] SSL: caching CA certificates
Sergey Kandaurov
pluknet at nginx.com
Wed Aug 21 22:04:57 UTC 2024
# HG changeset patch
# User Sergey Kandaurov <pluknet at nginx.com>
# Date 1721762968 0
# Tue Jul 23 19:29:28 2024 +0000
# Node ID 5768ea868d214021c7774ffa7d67d17f022fdb58
# Parent 09a8f17d87eccf5ae735815b50c9ee701d2a4ff7
SSL: caching CA certificates.
This can potentially provide a large amount of savings,
because CA certificates can be quite large.
Based on previous work by Mini Hawthorne.
diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -22,6 +22,8 @@ static ngx_inline ngx_int_t ngx_ssl_cert
static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store);
static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where,
int ret);
+static int ngx_ssl_cmp_x509_name(const X509_NAME *const *a,
+ const X509_NAME *const *b);
static void ngx_ssl_passwords_cleanup(void *data);
static int ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn,
ngx_ssl_session_t *sess);
@@ -658,6 +660,12 @@ ngx_int_t
ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
ngx_int_t depth)
{
+ int n, i;
+ char *err;
+ X509 *x509;
+ X509_NAME *name;
+ X509_STORE *store;
+ STACK_OF(X509) *chain;
STACK_OF(X509_NAME) *list;
SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ngx_ssl_verify_callback);
@@ -668,33 +676,85 @@ ngx_ssl_client_certificate(ngx_conf_t *c
return NGX_OK;
}
- if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
+ list = sk_X509_NAME_new(ngx_ssl_cmp_x509_name);
+ if (list == NULL) {
+ return NGX_ERROR;
+ }
+
+ store = SSL_CTX_get_cert_store(ssl->ctx);
+
+ if (store == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "SSL_CTX_get_cert_store() failed");
return NGX_ERROR;
}
- if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL)
- == 0)
- {
- ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
- "SSL_CTX_load_verify_locations(\"%s\") failed",
- cert->data);
+ chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CA, &err, cert, NULL);
+
+ if (chain == NULL) {
+ if (err != NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "cannot load certificate \"%s\": %s",
+ cert->data, err);
+ }
+
+ sk_X509_NAME_pop_free(list, X509_NAME_free);
return NGX_ERROR;
}
- /*
- * SSL_CTX_load_verify_locations() may leave errors in the error queue
- * while returning success
- */
-
- ERR_clear_error();
-
- list = SSL_load_client_CA_file((char *) cert->data);
-
- if (list == NULL) {
- ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
- "SSL_load_client_CA_file(\"%s\") failed", cert->data);
- return NGX_ERROR;
- }
+ n = sk_X509_num(chain);
+
+ for (i = 0; i < n; i++) {
+ x509 = sk_X509_value(chain, i);
+
+ if (X509_STORE_add_cert(store, x509) != 1) {
+
+ if (ngx_ssl_cert_already_in_hash()) {
+ continue;
+ }
+
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "X509_STORE_add_cert(\"%s\") failed", cert->data);
+ sk_X509_NAME_pop_free(list, X509_NAME_free);
+ sk_X509_pop_free(chain, X509_free);
+ return NGX_ERROR;
+ }
+
+ name = X509_get_subject_name(x509);
+ if (name == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "X509_get_subject_name(\"%s\") failed", cert->data);
+ sk_X509_NAME_pop_free(list, X509_NAME_free);
+ sk_X509_pop_free(chain, X509_free);
+ return NGX_ERROR;
+ }
+
+ name = X509_NAME_dup(name);
+ if (name == NULL) {
+ sk_X509_NAME_pop_free(list, X509_NAME_free);
+ sk_X509_pop_free(chain, X509_free);
+ return NGX_ERROR;
+ }
+
+#ifdef OPENSSL_IS_BORINGSSL
+ if (sk_X509_NAME_find(list, NULL, name) > 0) {
+#else
+ if (sk_X509_NAME_find(list, name) >= 0) {
+#endif
+ X509_NAME_free(name);
+ continue;
+ }
+
+ if (sk_X509_NAME_push(list, name) == 0) {
+ sk_X509_NAME_pop_free(list, X509_NAME_free);
+ sk_X509_pop_free(chain, X509_free);
+ X509_NAME_free(name);
+ return NGX_ERROR;
+ }
+
+ }
+
+ sk_X509_pop_free(chain, X509_free);
SSL_CTX_set_client_CA_list(ssl->ctx, list);
@@ -706,6 +766,12 @@ ngx_int_t
ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
ngx_int_t depth)
{
+ int i, n;
+ char *err;
+ X509 *x509;
+ X509_STORE *store;
+ STACK_OF(X509) *chain;
+
SSL_CTX_set_verify(ssl->ctx, SSL_CTX_get_verify_mode(ssl->ctx),
ngx_ssl_verify_callback);
@@ -715,25 +781,45 @@ ngx_ssl_trusted_certificate(ngx_conf_t *
return NGX_OK;
}
- if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
- return NGX_ERROR;
- }
-
- if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL)
- == 0)
- {
+ store = SSL_CTX_get_cert_store(ssl->ctx);
+
+ if (store == NULL) {
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
- "SSL_CTX_load_verify_locations(\"%s\") failed",
- cert->data);
+ "SSL_CTX_get_cert_store() failed");
+ return NGX_ERROR;
+ }
+
+ chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CA, &err, cert, NULL);
+
+ if (chain == NULL) {
+ if (err != NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "cannot load certificate \"%s\": %s",
+ cert->data, err);
+ }
+
return NGX_ERROR;
}
- /*
- * SSL_CTX_load_verify_locations() may leave errors in the error queue
- * while returning success
- */
-
- ERR_clear_error();
+ n = sk_X509_num(chain);
+
+ for (i = 0; i < n; i++) {
+ x509 = sk_X509_value(chain, i);
+
+ if (X509_STORE_add_cert(store, x509) != 1) {
+
+ if (ngx_ssl_cert_already_in_hash()) {
+ continue;
+ }
+
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "X509_STORE_add_cert(\"%s\") failed", cert->data);
+ sk_X509_pop_free(chain, X509_free);
+ return NGX_ERROR;
+ }
+ }
+
+ sk_X509_pop_free(chain, X509_free);
return NGX_OK;
}
@@ -985,6 +1071,13 @@ ngx_ssl_info_callback(const ngx_ssl_conn
}
+static int
+ngx_ssl_cmp_x509_name(const X509_NAME *const *a, const X509_NAME *const *b)
+{
+ return (X509_NAME_cmp(*a, *b));
+}
+
+
ngx_array_t *
ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file)
{
diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h
+++ b/src/event/ngx_event_openssl.h
@@ -205,6 +205,7 @@ typedef struct {
#define NGX_SSL_CACHE_CERT 0
#define NGX_SSL_CACHE_KEY 1
#define NGX_SSL_CACHE_CRL 2
+#define NGX_SSL_CACHE_CA 3
ngx_int_t ngx_ssl_init(ngx_log_t *log);
diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c
--- a/src/event/ngx_event_openssl_cache.c
+++ b/src/event/ngx_event_openssl_cache.c
@@ -53,6 +53,8 @@ static void *ngx_ssl_cache_crl_create(ng
static void ngx_ssl_cache_crl_free(void *data);
static void *ngx_ssl_cache_crl_ref(char **err, void *data);
+static void *ngx_ssl_cache_ca_create(ngx_str_t *id, char **err, void *data);
+
static BIO *ngx_ssl_cache_create_bio(ngx_str_t *id, char **err);
static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle);
@@ -100,6 +102,11 @@ static ngx_ssl_cache_type_t ngx_ssl_cac
{ ngx_ssl_cache_crl_create,
ngx_ssl_cache_crl_free,
ngx_ssl_cache_crl_ref },
+
+ /* NGX_SSL_CACHE_CA */
+ { ngx_ssl_cache_ca_create,
+ ngx_ssl_cache_cert_free,
+ ngx_ssl_cache_cert_ref }
};
@@ -577,6 +584,64 @@ ngx_ssl_cache_crl_ref(char **err, void *
}
+static void *
+ngx_ssl_cache_ca_create(ngx_str_t *id, char **err, void *data)
+{
+ BIO *bio;
+ X509 *x509;
+ u_long n;
+ STACK_OF(X509) *chain;
+
+ chain = sk_X509_new_null();
+ if (chain == NULL) {
+ *err = "sk_X509_new_null() failed";
+ return NULL;
+ }
+
+ bio = ngx_ssl_cache_create_bio(id, err);
+ if (bio == NULL) {
+ sk_X509_pop_free(chain, X509_free);
+ return NULL;
+ }
+
+ for ( ;; ) {
+
+ x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
+ if (x509 == NULL) {
+ n = ERR_peek_last_error();
+
+ if (ERR_GET_LIB(n) == ERR_LIB_PEM
+ && ERR_GET_REASON(n) == PEM_R_NO_START_LINE
+ && sk_X509_num(chain) > 0)
+ {
+ /* end of file */
+ ERR_clear_error();
+ break;
+ }
+
+ /* some real error */
+
+ *err = "PEM_read_bio_X509_AUX() failed";
+ BIO_free(bio);
+ sk_X509_pop_free(chain, X509_free);
+ return NULL;
+ }
+
+ if (sk_X509_push(chain, x509) == 0) {
+ *err = "sk_X509_push() failed";
+ BIO_free(bio);
+ X509_free(x509);
+ sk_X509_pop_free(chain, X509_free);
+ return NULL;
+ }
+ }
+
+ BIO_free(bio);
+
+ return chain;
+}
+
+
static BIO *
ngx_ssl_cache_create_bio(ngx_str_t *id, char **err)
{
More information about the nginx-devel
mailing list