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.
This commit is contained in:
Sergey Kandaurov 2024-09-09 19:05:58 +04:00 committed by pluknet
parent 61314518de
commit 5917e9de5a
3 changed files with 194 additions and 37 deletions

View File

@ -22,6 +22,8 @@ static ngx_inline ngx_int_t ngx_ssl_cert_already_in_hash(void);
static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store); 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, static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where,
int ret); 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 void ngx_ssl_passwords_cleanup(void *data);
static int ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn, static int ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn,
ngx_ssl_session_t *sess); ngx_ssl_session_t *sess);
@ -656,6 +658,12 @@ ngx_int_t
ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
ngx_int_t depth) 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; STACK_OF(X509_NAME) *list;
SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ngx_ssl_verify_callback); SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ngx_ssl_verify_callback);
@ -666,34 +674,84 @@ ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
return NGX_OK; return NGX_OK;
} }
if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { list = sk_X509_NAME_new(ngx_ssl_cmp_x509_name);
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);
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) { if (list == NULL) {
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
"SSL_load_client_CA_file(\"%s\") failed", cert->data);
return NGX_ERROR; 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;
}
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;
}
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); SSL_CTX_set_client_CA_list(ssl->ctx, list);
return NGX_OK; return NGX_OK;
@ -704,6 +762,12 @@ ngx_int_t
ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
ngx_int_t depth) 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), SSL_CTX_set_verify(ssl->ctx, SSL_CTX_get_verify_mode(ssl->ctx),
ngx_ssl_verify_callback); ngx_ssl_verify_callback);
@ -713,25 +777,44 @@ ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
return NGX_OK; return NGX_OK;
} }
if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { store = SSL_CTX_get_cert_store(ssl->ctx);
return NGX_ERROR;
}
if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL) if (store == NULL) {
== 0)
{
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
"SSL_CTX_load_verify_locations(\"%s\") failed", "SSL_CTX_get_cert_store() failed");
cert->data);
return NGX_ERROR; return NGX_ERROR;
} }
/* chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CA, &err, cert, NULL);
* SSL_CTX_load_verify_locations() may leave errors in the error queue if (chain == NULL) {
* while returning success if (err != NULL) {
*/ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
"cannot load certificate \"%s\": %s",
cert->data, err);
}
ERR_clear_error(); 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_pop_free(chain, X509_free);
return NGX_ERROR;
}
}
sk_X509_pop_free(chain, X509_free);
return NGX_OK; return NGX_OK;
} }
@ -987,6 +1070,13 @@ 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)
{
return (X509_NAME_cmp(*a, *b));
}
ngx_array_t * ngx_array_t *
ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file) ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file)
{ {

View File

@ -196,6 +196,7 @@ typedef struct {
#define NGX_SSL_CACHE_CERT 0 #define NGX_SSL_CACHE_CERT 0
#define NGX_SSL_CACHE_PKEY 1 #define NGX_SSL_CACHE_PKEY 1
#define NGX_SSL_CACHE_CRL 2 #define NGX_SSL_CACHE_CRL 2
#define NGX_SSL_CACHE_CA 3
ngx_int_t ngx_ssl_init(ngx_log_t *log); ngx_int_t ngx_ssl_init(ngx_log_t *log);

View File

@ -70,6 +70,9 @@ static void *ngx_ssl_cache_crl_create(ngx_ssl_cache_key_t *id, char **err,
static void ngx_ssl_cache_crl_free(void *data); 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_crl_ref(char **err, void *data);
static void *ngx_ssl_cache_ca_create(ngx_ssl_cache_key_t *id, char **err,
void *data);
static BIO *ngx_ssl_cache_create_bio(ngx_ssl_cache_key_t *id, char **err); static BIO *ngx_ssl_cache_create_bio(ngx_ssl_cache_key_t *id, char **err);
static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle);
@ -117,6 +120,11 @@ static ngx_ssl_cache_type_t ngx_ssl_cache_types[] = {
{ ngx_ssl_cache_crl_create, { ngx_ssl_cache_crl_create,
ngx_ssl_cache_crl_free, ngx_ssl_cache_crl_free,
ngx_ssl_cache_crl_ref }, ngx_ssl_cache_crl_ref },
/* NGX_SSL_CACHE_CA */
{ ngx_ssl_cache_ca_create,
ngx_ssl_cache_cert_free,
ngx_ssl_cache_cert_ref }
}; };
@ -623,6 +631,64 @@ ngx_ssl_cache_crl_ref(char **err, void *data)
} }
static void *
ngx_ssl_cache_ca_create(ngx_ssl_cache_key_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 * static BIO *
ngx_ssl_cache_create_bio(ngx_ssl_cache_key_t *id, char **err) ngx_ssl_cache_create_bio(ngx_ssl_cache_key_t *id, char **err)
{ {