Project

General

Profile

Feature #2692 ยป 0006-network.c-allow-specifying-server-cert-chain_patch.txt

mackyle, 2015-12-03 22:51

 
From 6b45eeae4441a5a7a88f63a8c92ef26842a5f663 Mon Sep 17 00:00:00 2001
From: "Kyle J. McKay" <mackyle@gmail.com>
Date: Thu, 3 Dec 2015 11:20:34 -0800
Subject: [PATCH] network.c: allow specifying server cert chain

To explain why this is needed, consider this comment from the
Apache mod_ssl ssl_engine_init.c source file:

/*
* Optionally configure extra server certificate chain certificates.
* This is usually done by OpenSSL automatically when one of the
* server cert issuers are found under SSLCACertificatePath or in
* SSLCACertificateFile. But because these are intended for client
* authentication it can conflict. For instance when you use a
* Global ID server certificate you've to send out the intermediate
* CA certificate, too. When you would just configure this with
* SSLCACertificateFile and also use client authentication mod_ssl
* would accept all clients also issued by this CA. Obviously this
* isn't what we want in this situation. So this feature here exists
* to allow one to explicity configure CA certificates which are
* used only for the server certificate chain.
*/

Note that SSLCACertificateFile corresponds to lighttpd's ssl.ca-file
directive.

However, lighttpd does not have an explicit directive to set the server's
sertificate chain. It sets only the server's certificate (excluding any
necessary intermediate certificates) using the SSL_CTX_use_certificate
function. The server's certificate chain, if it were to be configured
explicitly, would be set using the SSL_CTX_add_extra_chain_cert function.

However, lighttpd never calls the SSL_CTX_add_extra_chain_cert function.

But consider this information from the man page documentation about the
SSL_CTX_use_certificate function:

SSL_CTX_use_certificate() loads the certificate x into ctx,
SSL_use_certificate() loads x into ssl. The rest of the certificates
needed to form the complete certificate chain can be specified using
the SSL_CTX_add_extra_chain_cert(3) function.

SSL_CTX_use_certificate_file() loads the first certificate stored in
file into ctx. The formatting type of the certificate must be specified
from the known types SSL_FILETYPE_PEM, SSL_FILETYPE_ASN1.
SSL_use_certificate_file() loads the certificate from file into ssl.
See the NOTES section on why SSL_CTX_use_certificate_chain_file()
should be preferred.

SSL_CTX_use_certificate_chain_file() loads a certificate chain from
file into ctx. The certificates must be in PEM format and must be
sorted starting with the subject's certificate (actual client or server
certificate), followed by intermediate CA certificates if applicable,
and ending at the highest level (root) CA. There is no corresponding
function working on a single SSL object.

SSL_CTX_use_certificate_chain_file() is only applicable to PEM
formatting. Files of type SSL_FILETYPE_PEM can contain more than one
item.

SSL_CTX_use_certificate_chain_file() adds the first certificate found
in the file to the certificate store. The other certificates are added
to the store of chain certificates using
SSL_CTX_add_extra_chain_cert(3). There exists only one extra chain
store, so that the same chain is appended to both types of
certificates, RSA and DSA! If it is not intended to use both type of
certificate at the same time, it is recommended to use the
SSL_CTX_use_certificate_chain_file() instead of the
SSL_CTX_use_certificate_file() function in order to allow the use of
complete certificate chains even when no trusted CA storage is used or
when the CA issuing the certificate shall not be added to the trusted
CA storage.

We could switch from using the SSL_CTX_use_certificate function to the
SSL_CTX_use_certificate_chain_file function instead so that an optional
server certificate chain can simply be concatenated onto the end of the
file specified using the ssl.pemfile directive.

This requires only the very simplest change.

The problem with such a change is that the file may not be read until
after lighttpd has dropped privileges. If the file has restricted
privileges because it contains the server certificate's key it may not
be readable at that time.

However, the solution is simple. Just set a separate server certificate
key file with restricted permissions and relax the permissions on the
pemfile that then contains only non-sensitive certificates but no key.

Of course lighttpd has no ssl.keyfile directive making this impossible.

The alternative is to load the entire chain at the time the ssl.pemfile
is originally read and then call SSL_CTX_add_extra_chain_cert for each
extra certificate in the chain.

The final solution requires a bit more code than just using
SSL_CTX_use_certificate_chain_file would, but it's more compatible.

Also, while we we're mucking around with the SSL function calls, we take
the opportunity to correct the error return checking. Most SSL function
calls are documented as returning 1 on success. If it's not 1 then it
cannot be assumed to be successful, so checking for '< 0' for failure
or '!0' for success is simply incorrect. We correct all these checks.

Signed-off-by: Kyle J. McKay <mackyle@gmail.com>
---
src/base.h | 2 +-
src/configfile.c | 4 ++--
src/network.c | 64 +++++++++++++++++++++++++++++++++++++++++++-------------
src/server.c | 2 +-
4 files changed, 54 insertions(+), 18 deletions(-)

diff --git a/src/base.h b/src/base.h
index 4c748a57..12c91d2f 100644
--- a/src/base.h
+++ b/src/base.h
@@ -322,7 +322,7 @@ typedef struct {
SSL_CTX *ssl_ctx; /* not patched */
/* SNI per host: with COMP_SERVER_SOCKET, COMP_HTTP_SCHEME, COMP_HTTP_HOST */
EVP_PKEY *ssl_pemfile_pkey;
- X509 *ssl_pemfile_x509;
+ STACK_OF(X509) *ssl_pemfile_x509s;
STACK_OF(X509_NAME) *ssl_ca_file_cert_names;
#endif
} specific_config;
diff --git a/src/configfile.c b/src/configfile.c
index 72dabb70..8d47ae61 100644
--- a/src/configfile.c
+++ b/src/configfile.c
@@ -355,7 +355,7 @@ int config_setup_connection(server *srv, connection *con) {
PATCH(ssl_pemfile);
#ifdef USE_OPENSSL
- PATCH(ssl_pemfile_x509);
+ PATCH(ssl_pemfile_x509s);
PATCH(ssl_pemfile_pkey);
#endif
PATCH(ssl_ca_file);
@@ -429,7 +429,7 @@ int config_patch_connection(server *srv, connection *con, comp_key_t comp) {
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssl.pemfile"))) {
PATCH(ssl_pemfile);
#ifdef USE_OPENSSL
- PATCH(ssl_pemfile_x509);
+ PATCH(ssl_pemfile_x509s);
PATCH(ssl_pemfile_pkey);
#endif
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssl.ca-file"))) {
diff --git a/src/network.c b/src/network.c
index 24a435c2..fd77fc34 100644
--- a/src/network.c
+++ b/src/network.c
@@ -112,7 +112,7 @@ static int network_ssl_servername_callback(SSL *ssl, int *al, server *srv) {
config_patch_connection(srv, con, COMP_HTTP_SCHEME);
config_patch_connection(srv, con, COMP_HTTP_HOST);
- if (NULL == con->conf.ssl_pemfile_x509 || NULL == con->conf.ssl_pemfile_pkey) {
+ if (NULL == con->conf.ssl_pemfile_x509s || NULL == con->conf.ssl_pemfile_pkey) {
/* x509/pkey available <=> pemfile was set <=> pemfile got patched: so this should never happen, unless you nest $SERVER["socket"] */
log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:",
"no certificate/private key for TLS server name", con->tlsext_server_name);
@@ -120,14 +120,14 @@ static int network_ssl_servername_callback(SSL *ssl, int *al, server *srv) {
}
/* first set certificate! setting private key checks whether certificate matches it */
- if (!SSL_use_certificate(ssl, con->conf.ssl_pemfile_x509)) {
+ if (SSL_use_certificate(ssl, sk_X509_value(con->conf.ssl_pemfile_x509s, 0)) != 1) {
log_error_write(srv, __FILE__, __LINE__, "ssb:s", "SSL:",
"failed to set certificate for TLS server name", con->tlsext_server_name,
ERR_error_string(ERR_get_error(), NULL));
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
- if (!SSL_use_PrivateKey(ssl, con->conf.ssl_pemfile_pkey)) {
+ if (SSL_use_PrivateKey(ssl, con->conf.ssl_pemfile_pkey) != 1) {
log_error_write(srv, __FILE__, __LINE__, "ssb:s", "SSL:",
"failed to set private key for TLS server name", con->tlsext_server_name,
ERR_error_string(ERR_get_error(), NULL));
@@ -522,35 +522,71 @@ typedef enum {
} network_backend_t;
#ifdef USE_OPENSSL
-static X509* x509_load_pem_file(server *srv, const char *file) {
+static STACK_OF(X509)* x509s_load_pem_file(server *srv, const char *file) {
BIO *in;
X509 *x = NULL;
+ STACK_OF(X509) *x509s = NULL;
+ int err = 0;
in = BIO_new(BIO_s_file());
if (NULL == in) {
log_error_write(srv, __FILE__, __LINE__, "S", "SSL: BIO_new(BIO_s_file()) failed");
goto error;
}
+ x509s = sk_X509_new_null();
+ if (NULL == x509s) {
+ log_error_write(srv, __FILE__, __LINE__, "S", "SSL: sk_X509_new() failed");
+ goto error;
+ }
if (BIO_read_filename(in,file) <= 0) {
log_error_write(srv, __FILE__, __LINE__, "SSS", "SSL: BIO_read_filename('", file,"') failed");
goto error;
}
- x = PEM_read_bio_X509(in, NULL, NULL, NULL);
+ while (NULL != (x = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
+ if (!sk_X509_push(x509s, x)) {
+ log_error_write(srv, __FILE__, __LINE__, "S", "SSL: sk_X509_push() failed");
+ goto error;
+ }
+ }
+ if (sk_X509_num(x509s) > 0 && (err = ERR_peek_error()) > 0) {
+ if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE) {
+ while (ERR_get_error() > 0) {
+ /* discard EOF error */
+ }
+ err = 0;
+ }
+ }
- if (NULL == x) {
- log_error_write(srv, __FILE__, __LINE__, "SSS", "SSL: couldn't read X509 certificate from '", file,"'");
+ if (sk_X509_num(x509s) < 1 || err > 0) {
+ log_error_write(srv, __FILE__, __LINE__, "SSS", "SSL: couldn't read X509 certificate(s) from '", file,"'");
goto error;
}
BIO_free(in);
- return x;
+ return x509s;
error:
if (NULL != in) BIO_free(in);
+ if (NULL != x509s) sk_X509_pop_free(x509s, X509_free);
return NULL;
}
+static int SSL_CTX_use_certificate_chain(SSL_CTX *ctx, STACK_OF(X509) *x509s) {
+ int i;
+
+ if (sk_X509_num(x509s) < 1 || SSL_CTX_use_certificate(ctx, sk_X509_value(x509s, 0)) != 1) {
+ return 0;
+ }
+
+ for (i=1; i < sk_X509_num(x509s); ++i) {
+ if (SSL_CTX_add_extra_chain_cert(ctx, X509_dup(sk_X509_value(x509s, i))) != 1)
+ return 0;
+ }
+
+ return 1;
+}
+
static EVP_PKEY* evp_pkey_load_pem_file(server *srv, const char *file) {
BIO *in;
EVP_PKEY *x = NULL;
@@ -595,10 +631,10 @@ static int network_openssl_load_pemfile(server *srv, size_t ndx) {
}
#endif
- if (NULL == (s->ssl_pemfile_x509 = x509_load_pem_file(srv, s->ssl_pemfile->ptr))) return -1;
+ if (NULL == (s->ssl_pemfile_x509s = x509s_load_pem_file(srv, s->ssl_pemfile->ptr))) return -1;
if (NULL == (s->ssl_pemfile_pkey = evp_pkey_load_pem_file(srv, s->ssl_pemfile->ptr))) return -1;
- if (!X509_check_private_key(s->ssl_pemfile_x509, s->ssl_pemfile_pkey)) {
+ if (!X509_check_private_key(sk_X509_value(s->ssl_pemfile_x509s, 0), s->ssl_pemfile_pkey)) {
log_error_write(srv, __FILE__, __LINE__, "sssb", "SSL:",
"Private key does not match the certificate public key, reason:",
ERR_error_string(ERR_get_error(), NULL),
@@ -892,13 +928,13 @@ int network_init(server *srv) {
SSL_CTX_set_verify_depth(s->ssl_ctx, s->ssl_verifyclient_depth);
}
- if (SSL_CTX_use_certificate(s->ssl_ctx, s->ssl_pemfile_x509) < 0) {
+ if (SSL_CTX_use_certificate_chain(s->ssl_ctx, s->ssl_pemfile_x509s) != 1) {
log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:",
ERR_error_string(ERR_get_error(), NULL), s->ssl_pemfile);
return -1;
}
- if (SSL_CTX_use_PrivateKey(s->ssl_ctx, s->ssl_pemfile_pkey) < 0) {
+ if (SSL_CTX_use_PrivateKey(s->ssl_ctx, s->ssl_pemfile_pkey) != 1) {
log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:",
ERR_error_string(ERR_get_error(), NULL), s->ssl_pemfile);
return -1;
@@ -915,8 +951,8 @@ int network_init(server *srv) {
SSL_CTX_set_mode(s->ssl_ctx, SSL_CTX_get_mode(s->ssl_ctx) | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
# ifndef OPENSSL_NO_TLSEXT
- if (!SSL_CTX_set_tlsext_servername_callback(s->ssl_ctx, network_ssl_servername_callback) ||
- !SSL_CTX_set_tlsext_servername_arg(s->ssl_ctx, srv)) {
+ if (SSL_CTX_set_tlsext_servername_callback(s->ssl_ctx, network_ssl_servername_callback) != 1 ||
+ SSL_CTX_set_tlsext_servername_arg(s->ssl_ctx, srv) != 1) {
log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
"failed to initialize TLS servername callback, openssl library does not support TLS servername extension");
return -1;
diff --git a/src/server.c b/src/server.c
index e096de1b..9697010a 100644
--- a/src/server.c
+++ b/src/server.c
@@ -315,7 +315,7 @@ static void server_free(server *srv) {
#ifdef USE_OPENSSL
SSL_CTX_free(s->ssl_ctx);
EVP_PKEY_free(s->ssl_pemfile_pkey);
- X509_free(s->ssl_pemfile_x509);
+ if (NULL != s->ssl_pemfile_x509s) sk_X509_pop_free(s->ssl_pemfile_x509s, X509_free);
if (NULL != s->ssl_ca_file_cert_names) sk_X509_NAME_pop_free(s->ssl_ca_file_cert_names, X509_NAME_free);
#endif
free(s);
--
2.4.10

    (1-1/1)