|
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
|
|
|