Provide a mechanism to setup TLS cert chain
This commit from @jwwilcox, together with a corresponding commit to
_netopeer2_, fixes the TLS connection scenario in which the server's
certificate has been signed by an intermediate CA, but the client only has
the root CA available locally. In this case, the client will reject the
connection attempt, because it does not know about the intermediate CA.
The changes here use the new _netopeer2_ callback (which supplies the
intermediate certificate(s)) to call `SSL_CTX_add_extra_chain_cert()`,
which allows the server's TLS context to automatically provide the intermediate
certificate(s) to the client.
This scenario is demonstrated in the integration test
`test_tls_client_missing_server_intermediate()` in
[ADTRAN:netopeer2-integration-tests](https://github.com/ADTRAN/netopeer2-integration-tests/blob/master/tests/test_tls.py#L73).
The changes here, together with the corresponding commit in _netopeer2_, will
allow [the currently failing test case](https://travis-ci.org/ADTRAN/netopeer2-integration-tests/jobs/420293391#L7434)
to pass.
diff --git a/src/session_server_tls.c b/src/session_server_tls.c
index 70ac35d..9a529dc 100644
--- a/src/session_server_tls.c
+++ b/src/session_server_tls.c
@@ -986,6 +986,21 @@
server_opts.server_cert_data_free = free_user_data;
}
+API void
+nc_server_tls_set_server_cert_chain_clb(int (*cert_chain_clb)(const char *name, void *user_data, char ***cert_paths,
+ int *cert_path_count, char ***cert_data, int *cert_data_count),
+ void *user_data, void (*free_user_data)(void *user_data))
+{
+ if (!cert_chain_clb) {
+ ERRARG("cert_chain_clb");
+ return;
+ }
+
+ server_opts.server_cert_chain_clb = cert_chain_clb;
+ server_opts.server_cert_chain_data = user_data;
+ server_opts.server_cert_chain_data_free = free_user_data;
+}
+
static int
nc_server_tls_add_trusted_cert_list(const char *name, struct nc_server_tls_opts *opts)
{
@@ -1705,6 +1720,78 @@
pthread_key_create(&verify_key, NULL);
}
+static X509*
+tls_load_cert(const char *cert_path, const char *cert_data)
+{
+ X509 *cert;
+
+ if (cert_path) {
+ cert = pem_to_cert(cert_path);
+ } else {
+ cert = base64der_to_cert(cert_data);
+ }
+
+ if (!cert) {
+ if (cert_path) {
+ ERR("Loading a trusted certificate (path \"%s\") failed (%s).", cert_path,
+ ERR_reason_error_string(ERR_get_error()));
+ } else {
+ ERR("Loading a trusted certificate (data \"%s\") failed (%s).", cert_data,
+ ERR_reason_error_string(ERR_get_error()));
+ }
+ }
+ return cert;
+}
+
+static int
+nc_tls_ctx_set_server_cert_chain(SSL_CTX *tls_ctx, const char *cert_name)
+{
+ char **cert_paths = NULL, **cert_data = NULL;
+ int cert_path_count = 0, cert_data_count = 0, ret = 0, i = 0;
+ X509 *cert = NULL;
+
+ if (!server_opts.server_cert_chain_clb) {
+ /* This is optional, so return OK */
+ return 0;
+ }
+
+ if (server_opts.server_cert_chain_clb(cert_name, server_opts.server_cert_chain_data, &cert_paths,
+ &cert_path_count, &cert_data, &cert_data_count)) {
+ ERR("Server certificate chain callback failed.");
+ return -1;
+ }
+
+ for (i = 0; i < cert_path_count; ++i) {
+ cert = tls_load_cert(cert_paths[i], NULL);
+ if (!cert || SSL_CTX_add_extra_chain_cert(tls_ctx, cert) != 1) {
+ ERR("Loading the server certificate chain failed (%s).", ERR_reason_error_string(ERR_get_error()));
+ ret = -1;
+ goto cleanup;
+ }
+ }
+
+ for (i = 0; i < cert_data_count; ++i) {
+ cert = tls_load_cert(NULL, cert_data[i]);
+ if (!cert || SSL_CTX_add_extra_chain_cert(tls_ctx, cert) != 1) {
+ ERR("Loading the server certificate chain failed (%s).", ERR_reason_error_string(ERR_get_error()));
+ ret = -1;
+ goto cleanup;
+ }
+ }
+cleanup:
+ for (i = 0; i < cert_path_count; ++i) {
+ free(cert_paths[i]);
+ }
+ free(cert_paths);
+ for (i = 0; i < cert_data_count; ++i) {
+ free(cert_data[i]);
+ }
+ free(cert_data);
+ /* cert is owned by the SSL_CTX */
+
+ return ret;
+}
+
static int
nc_tls_ctx_set_server_cert_key(SSL_CTX *tls_ctx, const char *cert_name)
{
@@ -1759,6 +1846,8 @@
}
}
+ ret = nc_tls_ctx_set_server_cert_chain(tls_ctx, cert_name);
+
cleanup:
X509_free(cert);
EVP_PKEY_free(pkey);
@@ -1772,22 +1861,8 @@
static void
tls_store_add_trusted_cert(X509_STORE *cert_store, const char *cert_path, const char *cert_data)
{
- X509 *cert;
-
- if (cert_path) {
- cert = pem_to_cert(cert_path);
- } else {
- cert = base64der_to_cert(cert_data);
- }
-
+ X509 *cert = tls_load_cert(cert_path, cert_data);
if (!cert) {
- if (cert_path) {
- ERR("Loading a trusted certificate (path \"%s\") failed (%s).", cert_path,
- ERR_reason_error_string(ERR_get_error()));
- } else {
- ERR("Loading a trusted certificate (data \"%s\") failed (%s).", cert_data,
- ERR_reason_error_string(ERR_get_error()));
- }
return;
}