mod_ssl_ct.c revision 9ee814f862f6f2203cf0d1859969682954dfa97a
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Issues
*
* . proxy: an httpd child process validates SCTs from a server only on the
* first time the data is received; but it could fail once due to invalid
* timestamp, and not be rechecked later after (potentially) time elapses
* and the timestamp is now in a valid range
* . server: shouldn't have to read file of server SCTs on every handshake
* (shared memory or cached file?)
* . split mod_ssl_ct.c into more pieces
* . research: Is it possible to send an SCT that is outside of the known
* valid interval for the log?
*/
#if defined(WIN32)
#define HAVE_SCT_DAEMON_THREAD
#else
#define HAVE_SCT_DAEMON_CHILD
#endif
#include <limits.h>
#if defined(HAVE_SCT_DAEMON_CHILD)
#include <unistd.h>
#endif
#include "apr_version.h"
#endif
#include "apr_escape.h"
#include "apr_global_mutex.h"
#include "apr_signal.h"
#include "apr_strings.h"
#include "apr_thread_rwlock.h"
#include "apr_dbd.h"
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_main.h"
#include "http_protocol.h"
#include "mpm_common.h"
#include "util_mutex.h"
#include "ap_listen.h"
#include "ap_mpm.h"
#include "unixd.h"
#endif
#include "mod_proxy.h"
#include "mod_ssl.h"
#include "mod_ssl_openssl.h"
#include "ssl_ct_util.h"
#include "ssl_ct_sct.h"
#if OPENSSL_VERSION_NUMBER < 0x10002001L
#error "mod_ssl_ct requires OpenSSL 1.0.2-beta1 or later"
#endif
#ifdef WIN32
#define DOTEXE ".exe"
#else
#define DOTEXE ""
#endif
#define STATUS_VAR "SSL_CT_PEER_STATUS"
#define STATUS_VAR_AWARE_VAL "peer-aware"
#define STATUS_VAR_UNAWARE_VAL "peer-unaware"
#define PROXY_SCT_SOURCES_VAR "SSL_PROXY_SCT_SOURCES"
#define DAEMON_NAME "SCT maintenance daemon"
#define SERVICE_THREAD_NAME "service thread"
/** Limit on size of stored SCTs for a certificate (individual SCTs as well
* as size of all.
*/
#define MAX_SCTS_SIZE 10000
/** Limit on size of log URL list for a certificate
*/
#define MAX_LOGLIST_SIZE 1000
typedef struct ct_server_config {
const char *sct_storage;
const char *audit_storage;
const char *ct_exe;
const char *log_config_fname;
int max_sh_sct;
#define PROXY_AWARENESS_UNSET -1
#define PROXY_OBLIVIOUS 1
#define PROXY_REQUIRE 3
int proxy_awareness;
typedef struct ct_conn_config {
int peer_ct_aware;
/* proxy mode only */
void *cert_sct_list;
void *serverhello_sct_list;
int ocsp_has_sct_list;
void *ocsp_sct_list;
typedef struct ct_server_cert_info {
const char *fingerprint;
const char *sct_dir;
typedef struct ct_sct_data {
const void *data;
} ct_sct_data;
typedef struct ct_callback_info {
server_rec *s;
conn_rec *c;
typedef struct ct_cached_server_data {
/* the log configuration in use -- either db_log_config or static_log_config */
static apr_array_header_t *active_log_config;
#define SSL_CT_MUTEX_TYPE "ssl-ct-sct-update"
static apr_global_mutex_t *ssl_ct_sct_update;
static apr_thread_t *service_thread;
static apr_hash_t *cached_server_data;
static const char *audit_fn_perm, *audit_fn_active;
static apr_file_t *audit_file;
static int audit_file_nonempty;
static apr_thread_mutex_t *audit_file_mutex;
static apr_thread_rwlock_t *log_config_rwlock;
#ifdef HAVE_SCT_DAEMON_CHILD
/* The APR other-child API doesn't tell us how the daemon exited
* (SIGSEGV vs. exit(1)). The other-child maintenance function
* needs to decide whether to restart the daemon after a failure
* based on whether or not it exited due to a fatal startup error
* or something that happened at steady-state. This exit status
* is unlikely to collide with exit signals.
*/
#define DAEMON_STARTUP_ERROR 254
static pid_t daemon_pid;
static int daemon_should_exit = 0;
#endif /* HAVE_SCT_DAEMON_CHILD */
#ifdef HAVE_SCT_DAEMON_THREAD
static apr_thread_t *daemon_thread;
#endif /* HAVE_SCT_DAEMON_THREAD */
{
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int n;
return apr_pescape_hex(p, md, n, 0);
}
/* a server's SCT-related storage on disk:
*
* <rootdir>/<fingerprint>/servercerts.pem
* Concatenation of leaf certificate and any
* configured intermediate certificates
*
* <rootdir>/<fingerprint>/logs
* List of log URLs, one per line; this is
* used to recognize when a log URL configuration
* change makes our current SCT set invalid
*
* <rootdir>/<fingerprint>/AUTO_hostname_port_uri.sct
* SCT for cert with this fingerprint
* from this log (could be any number
* of these)
*
* <rootdir>/<fingerprint>/<anything>.sct
* (file is optional; could be any number
* of these; should not start with "AUTO_")
* Note that the administrator should store
* statically maintained SCTs in a different
* directory for the server certificate (specified
* by the CTStaticSCTs directive). A hypothetical
* external mechanism for maintaining SCTs following
* some other model could store them here for use
* by the server.
*
* <rootdir>/<fingerprint>/collated
* one or more SCTs ready to send
* (this is all that the web server
* processes care about)
*
* Additionally, the CTStaticSCTs directive specifies a certificate-
* specific directory of statically-maintained SCTs to be sent.
*/
#define SERVERCERTS_BASENAME "servercerts.pem"
#define COLLATED_SCTS_BASENAME "collated"
#define LOGLIST_BASENAME "logs"
* files
*/
const char *cert_sct_dir,
const char *static_cert_sct_dir,
int max_sh_sct)
{
/* Read the various .sct files and stick them together in a single file */
apr_uint16_t overall_len = 0;
char *tmp_collated_fn, *collated_fn;
const char *cur_sct_file;
const char * const *elts;
int i, scts_written = 0, skipped = 0;
if (rv != APR_SUCCESS) {
return rv;
}
/* Note: We rebuild the file that combines the SCTs every time this
* code runs, even if no individual SCTs are new (or at least
* re-fetched).
* That allows the admin to see the last processing by looking
* at the timestamp.
* Rechecking even if no SCTs are new allows SCTs which were not
* yet valid originally (just submitted to a log) to be used as
* soon as practical.
*/
APR_FPROT_OS_DEFAULT, p);
if (rv != APR_SUCCESS) {
"can't create %s", tmp_collated_fn);
return rv;
}
/* stick a 0 len (for the list) at the start of the file;
* we'll have to patch that later
*/
if (rv != APR_SUCCESS) {
return rv;
}
if (rv != APR_SUCCESS) {
return rv;
}
if (static_cert_sct_dir) {
/* Add in any SCTs that the administrator has configured */
if (rv != APR_SUCCESS) {
return rv;
}
}
char *scts;
cur_sct_file = elts[i];
"Adding SCT from file %s", cur_sct_file);
if (rv != APR_SUCCESS) {
break;
}
if (rv != APR_SUCCESS) {
break;
}
/* If the SCT has a timestamp in the future, it may have just been
* created by the log.
*/
"SCT in file %s has timestamp in future (%s), skipping",
continue;
}
/* Only now do we know that the SCT is valid to send, so
* see if it has to be skipped by configured limit.
*/
if (scts_written >= max_sh_sct) {
skipped++;
continue;
}
if (rv != APR_SUCCESS) {
break;
}
if (rv != APR_SUCCESS) {
"can't write %hu bytes to %s",
break;
}
scts_written++;
}
"SCTs sent in ServerHello are limited to %d by "
"CTServerHelloSCTLimit (ignoring %d)",
skipped);
}
if (rv == APR_SUCCESS) {
if (rv == APR_SUCCESS) {
}
if (rv != APR_SUCCESS) {
"could not write the SCT list length at the start of the file");
}
}
if (tmprv != APR_SUCCESS) {
"error flushing and closing %s", tmp_collated_fn);
if (rv == APR_SUCCESS) {
}
}
if (replacing) {
"global mutex lock failed");
return rv;
}
}
if (rv != APR_SUCCESS) {
"couldn't rename %s to %s, no SCTs to send for now",
}
if (replacing) {
"global mutex unlock failed");
if (rv == APR_SUCCESS) {
}
}
}
}
return rv;
}
{
char *ch;
while (*ch) {
switch(*ch) {
/* chars that shouldn't be used in a filename */
case ':':
case '/':
case '\\':
case '*':
case '?':
case '<':
case '>':
case '"':
case '|':
*ch = '-';
}
++ch;
}
return fn;
}
const char *sct_fn)
{
const char *args[8];
int i;
i = 0;
args[i++] = "--http_log";
args[i++] = "--logtostderr";
args[i++] = "upload";
return rv;
}
const char *cert_file,
const char *cert_sct_dir,
{
char *sct_fn;
const char *log_url_basename;
if (rv != APR_SUCCESS) {
return rv;
}
if (rv == APR_SUCCESS) {
"Found SCT for %s in %s",
"SCT for %s is older than %d seconds, must refresh",
(int)(apr_time_sec(max_sct_age)));
}
else {
return APR_SUCCESS;
}
}
else {
/* no need to print error string for file-not-found err */
s,
"Did not find SCT for %s in %s, must fetch",
}
return rv;
}
{
apr_file_t *f;
int i;
APR_FPROT_OS_DEFAULT, p);
if (rv != APR_SUCCESS) {
"can't create %s", listfile);
return rv;
}
for (i = 0; i < log_config->nelts; i++) {
if (!config_elts[i]->uri_str) {
continue;
}
if (!log_valid_for_sent_sct(config_elts[i])) {
continue;
}
if (rv == APR_SUCCESS) {
}
if (rv != APR_SUCCESS) {
"error writing to %s", listfile);
break;
}
}
tmprv = apr_file_close(f);
if (tmprv != APR_SUCCESS) {
"error flushing and closing %s", listfile);
if (rv == APR_SUCCESS) {
}
}
return rv;
}
{
int i;
continue;
}
if (!log_valid_for_sent_sct(elts[i])) {
continue;
}
return 1;
}
}
return 0;
}
const char *cert_sct_dir,
{
/* The set of logs can change, and we need to remove SCTs retrieved
* from logs that we no longer trust. To track changes we'll use a
* file in the directory for the server certificate.
*
* (When can the set change? Right now they can only change at [re]start,
* but in the future we should be able to find the set of trusted logs
* dynamically.)
*/
if (rv != APR_SUCCESS) {
return rv;
}
if (ctutil_file_exists(p, listfile)) {
char **elts;
int i;
if (rv != APR_SUCCESS) {
return rv;
}
char *sct_for_log;
int exists;
if (rv != APR_SUCCESS) {
"unparseable log URL %s in file %s - ignoring",
/* some garbage in the file? can't map to an auto-maintained SCT,
* so just skip it
*/
continue;
}
"Log %s is no longer enabled%s",
elts[i],
if (exists) {
if (rv != APR_SUCCESS) {
"can't remove SCT %s from previously trusted log %s",
sct_for_log, elts[i]);
return rv;
}
}
}
}
}
else {
/* can't tell what was trusted before; just remove everything
* that was created automatically
*/
const char * const *elts;
int i;
"List of previous logs doesn't exist (%s), removing previously obtained SCTs",
listfile);
if (rv != APR_SUCCESS) {
return rv;
}
const char *cur_sct_file = elts[i];
"Removing %s", cur_sct_file);
if (rv != APR_SUCCESS) {
"can't remove %s", cur_sct_file);
}
}
}
if (rv == APR_SUCCESS) {
}
return rv;
}
const char *cert_sct_dir,
const char *static_cert_sct_dir,
const char *ct_exe,
int max_sh_sct)
{
char *cert_fn;
int i;
if (rv != APR_SUCCESS) {
return rv;
}
if (ct_exe) {
if (rv != APR_SUCCESS) {
return rv;
}
for (i = 0; i < log_config->nelts; i++) {
if (!config_elts[i]->url) {
continue;
}
if (!log_valid_for_sent_sct(config_elts[i])) {
continue;
}
&config_elts[i]->uri,
if (rv != APR_SUCCESS) {
return rv;
}
}
}
else {
/* Log client tool (from certificate-transparency open source project)
* not configured; we can only use admin-managed SCTs
*/
}
if (rv != APR_SUCCESS) {
return rv;
}
return rv;
}
{
server_rec *s = data;
int mpmq_s;
int count = 0;
SERVICE_THREAD_NAME " started");
while (1) {
break;
}
if (mpmq_s == AP_MPMQ_STOPPING) {
break;
}
if (++count >= 30) {
count = 0;
if (sconf->db_log_config) {
/* Reload log config DB */
SERVICE_THREAD_NAME " - reloading config");
sizeof(ct_log_config *));
s, sconf->log_config_fname,
if (rv != APR_SUCCESS) {
/* specific issue already logged */
SERVICE_THREAD_NAME " - no active configuration until "
"log config DB is corrected");
}
else {
}
}
}
}
SERVICE_THREAD_NAME " exiting");
return NULL;
}
{
return APR_SUCCESS;
}
{
"%s - reloading config", daemon_name);
sizeof(ct_log_config *));
if (rv != APR_SUCCESS) {
"%s - no active configuration until "
"log config DB is corrected", daemon_name);
return;
}
}
"%s - refreshing SCTs as needed", daemon_name);
if (rv != APR_SUCCESS) {
"%s - SCT refresh failed; will try again later",
}
}
#ifdef HAVE_SCT_DAEMON_CHILD
static void daemon_signal_handler(int sig)
{
}
}
{
int mpm_state;
int stopping;
switch (reason) {
case APR_OC_REASON_DEATH:
/* If apache is not terminating or restarting,
* restart the daemon
*/
* assume we shouldn't restart daemon
*/
mpm_state != AP_MPMQ_STOPPING) {
stopping = 0;
}
if (!stopping) {
if (status == DAEMON_STARTUP_ERROR) {
DAEMON_NAME " failed to initialize");
}
else {
DAEMON_NAME " process died, restarting");
}
}
break;
case APR_OC_REASON_RESTART:
/* don't do anything; server is stopping or restarting */
break;
case APR_OC_REASON_LOST:
/* Restart the child cgid daemon process */
break;
case APR_OC_REASON_UNREGISTER:
/* we get here when pcgi is cleaned up; pcgi gets cleaned
* up when pconf gets cleaned up
*/
break;
}
}
#endif
{
int rc;
/* Ignoring SIGCHLD results in errno ECHILD returned from apr_proc_wait().
* apr_signal(SIGCHLD, SIG_IGN);
*/
/* Close our copy of the listening sockets */
if (rv != APR_SUCCESS) {
"could not initialize " SSL_CT_MUTEX_TYPE
" mutex in " DAEMON_NAME);
return DAEMON_STARTUP_ERROR;
}
if (!geteuid()) {
/* Fix up permissions of the directories written to by the daemon
*/
int i;
if (sconf->audit_storage) {
}
&subdirs);
ap_unixd_config.group_id) < 0) {
"Couldn't change owner or group of directory %s",
elts[i]);
return errno;
}
}
}
else {
"Did not read any entries from %s (no server certificate?)",
sconf->sct_storage);
}
}
return rc;
}
/* ptemp - temporary pool for refresh cycles */
while (!daemon_should_exit) {
}
DAEMON_NAME " - exiting");
return 0;
}
{
daemon_should_exit = 0; /* clear setting from previous generation */
if ((daemon_pid = fork()) < 0) {
return DECLINED;
}
else if (daemon_pid == 0) {
apr_pool_create(&pdaemon, p);
}
}
#endif
return OK;
}
#endif /* HAVE_SCT_DAEMON_CHILD */
#ifdef HAVE_SCT_DAEMON_THREAD
{
server_rec *s = data;
int mpmq_s;
int count = 0;
DAEMON_THREAD_NAME " started");
/* ptemp - temporary pool for refresh cycles */
while (1) {
break;
}
if (mpmq_s == AP_MPMQ_STOPPING) {
break;
}
if (++count >= 30) {
count = 0;
}
}
DAEMON_THREAD_NAME " - exiting");
return NULL;
}
{
pconf);
if (rv != APR_SUCCESS) {
return HTTP_INTERNAL_SERVER_ERROR;
}
return OK;
}
#endif /* HAVE_SCT_DAEMON_THREAD */
{
return APR_SUCCESS;
}
{
server_rec *s;
s = s_main;
while (s) {
int i;
const ct_server_cert_info *cert_info_elts;
/* we may have already processed this cert for another
* server_rec
*/
const char *static_cert_sct_dir =
APR_HASH_KEY_STRING, "done");
sconf->max_sh_sct);
if (rv != APR_SUCCESS) {
return rv;
}
}
}
}
s = s->next;
}
return rv;
}
{
int num = 0;
server_rec *s;
s = s_main;
while (s) {
}
s = s->next;
}
return num;
}
{
#ifdef HAVE_SCT_DAEMON_CHILD
const char *userdata_key = "sct_daemon_init";
if (!procnew) {
}
#endif /* HAVE_SCT_DAEMON_CHILD */
if (num_server_certs(s_main) == 0) {
/* Theoretically this module could operate in a proxy-only
* configuration, where httpd does not act as a TLS server but proxy is
* configured as a TLS client. That isn't currently implemented.
*/
"No server certificates were found.");
"mod_ssl_ct only supports configurations with a TLS server.");
return HTTP_INTERNAL_SERVER_ERROR;
}
if (rv != APR_SUCCESS) {
"could not create global mutex");
return HTTP_INTERNAL_SERVER_ERROR;
}
if (sconf->log_config_fname) {
if (!sconf->db_log_config) {
/* log config db in separate pool that can be cleared */
sizeof(ct_log_config *));
}
if (rv != APR_SUCCESS) {
return HTTP_INTERNAL_SERVER_ERROR;
}
}
"Either the static log configuration or the db log "
"configuration must be empty");
return HTTP_INTERNAL_SERVER_ERROR;
}
}
}
}
else {
"No log URLs were configured; only admin-managed SCTs can be sent");
/* if a db is configured, it could be updated later */
* empty array */
sizeof(ct_log_config *));
}
}
/* Ensure that we already have, or can fetch, fresh SCTs for each
* certificate. If so, start the daemon to maintain these and let
* startup continue. (Otherwise abort startup.)
*
* Except when we start up as root. We don't want to run external
* certificate-transparency tools as root, and we don't want to have
* to fix up the permissions of everything we created so that the
* SCT maintenance daemon can continue to maintain the SCTs as the
*/
#if AP_NEED_SET_MUTEX_PERMS /* Unix :) */
if (!geteuid()) { /* root */
"SCTs will be fetched from configured logs as needed "
"and may not be available immediately");
}
else {
#endif
if (rv != APR_SUCCESS) {
"refresh_all_scts() failed");
return HTTP_INTERNAL_SERVER_ERROR;
}
}
#endif
#ifdef HAVE_SCT_DAEMON_CHILD
return ret;
}
}
#endif /* HAVE_SCT_DAEMON_CHILD */
#ifdef HAVE_SCT_DAEMON_THREAD
/* WIN32-ism: ensure this is the parent by checking AP_PARENT_PID,
* which is only set in WinNT children.
*/
&& !getenv("AP_PARENT_PID")) {
return ret;
}
}
#endif /* HAVE_SCT_DAEMON_THREAD */
return OK;
}
{
if (!sconf->sct_storage) {
"Directive CTSCTStorage is required");
return HTTP_INTERNAL_SERVER_ERROR;
}
if (!sconf->audit_storage) {
/* umm, hard to tell if needed... must have server with
* SSL proxy enabled and server-specific-sconf->proxy_awareness
* != PROXY_OBLIVIOUS...
*/
"Directive CTAuditStorage isn't set; proxy will not save "
"data for off-line audit");
}
"Directive CTLogClient isn't set; server certificates "
"can't be submitted to configured logs; only admin-"
"managed SCTs can be provided to clients");
}
if (sconf->log_config_fname) {
"Log config file %s cannot be read",
if (msg) {
"%s", msg);
}
return HTTP_INTERNAL_SERVER_ERROR;
}
}
return OK;
}
const char *sct_dir,
server_rec *s,
{
if (rv != APR_SUCCESS) {
return rv;
}
if (rv != APR_SUCCESS) {
return rv;
}
"global mutex lock failed");
return rv;
}
"global mutex unlock failed");
}
return rv;
}
{
X509 *x;
int i, rc;
char *cert_sct_dir, *servercerts_pem;
const char *fingerprint;
while (rc) {
if (x) {
if (!ctutil_dir_exists(p, cert_sct_dir)) {
if (rv != APR_SUCCESS) {
"can't create directory %s",
}
}
SERVERCERTS_BASENAME, p, s);
/* Not this: SSL_CTX_get0_chain_certs(ctx, &chain);
*
* See this thread:
* 201402.mbox/%3CCAKUrXK5-2_Sg8FokxBP8nW7tmSuTZZWL-%3
* DBDhNnwyK-Z4dmQiQ%40mail.gmail.com%3E
*/
if (chain) {
}
}
"wrote server cert and chain to %s", servercerts_pem);
}
else {
"could not find leaf certificate");
}
}
}
{
if (!conncfg) {
}
return conncfg;
}
static void client_is_ct_aware(conn_rec *c)
{
}
static int is_client_ct_aware(conn_rec *c)
{
return conncfg->peer_ct_aware;
}
static void server_cert_has_sct_list(conn_rec *c)
{
}
/* Look at SSLClient::VerifyCallback() and WriteSSLClientCTData()
* for validation and saving of data for auditing in a form that
* the c-t tools can use.
*/
{
int i;
for (i = 0; i < sk_X509_num(chain); i++) {
if (i == 0) {
}
}
return cc;
}
int i;
}
}
/* Create hash of leaf certificate and any SCTs so that
* we can determine whether or not we've seen this exact
* info from the server before.
*/
{
const char *fp;
unsigned char digest[SHA256_DIGEST_LENGTH];
if (conncfg->cert_sct_list) {
}
if (conncfg->serverhello_sct_list) {
}
if (conncfg->ocsp_sct_list) {
}
}
void *sct_list,
{
const unsigned char *mem, *start_of_data;
/* Make sure the overall length is correct */
if (rv != APR_SUCCESS) {
return rv;
}
return APR_EINVAL;
}
/* add each SCT in the list to the all_scts array */
&start_of_data, &len_of_data);
if (rv == APR_SUCCESS) {
}
}
return APR_EINVAL;
}
return APR_SUCCESS;
}
/* perform quick sanity check of server SCT(s) during handshake;
* errors should result in fatal alert
*/
{
if (conncfg->serverhello_sct_list) {
}
if (conncfg->cert_sct_list) {
}
if (conncfg->ocsp_sct_list) {
}
#endif /* httpd has ap_log_*data() */
}
/* deserialize all the SCTs */
if (conncfg->cert_sct_list) {
if (rv != APR_SUCCESS) {
"couldn't deserialize SCT list from certificate");
}
}
if (rv != APR_SUCCESS) {
"couldn't deserialize SCT list from ServerHello");
}
}
if (rv != APR_SUCCESS) {
"couldn't deserialize SCT list from stapled OCSP response");
}
}
if (rv == APR_SUCCESS) {
/* How did we get here without at least one SCT? */
"SNAFU: No deserialized SCTs found in validate_server_data()");
rv = APR_EINVAL;
}
else {
&fields);
if (tmprv != APR_SUCCESS) {
}
else {
if (tmprv != APR_SUCCESS) {
}
if (active_log_config) {
/* will only block if we have a DB-based log
* configuration which is currently being refreshed
*/
== APR_SUCCESS);
== APR_SUCCESS);
if (tmprv == APR_NOTFOUND) {
"Server sent SCT from unrecognized log");
}
else if (tmprv != APR_SUCCESS) {
"Server sent SCT with invalid signature");
tmprv = APR_EINVAL;
}
else {
}
}
else {
"Signature of SCT from server could not be "
"verified (no configured log public keys)");
}
}
}
if (verification_failures && !verification_successes) {
/* If no SCTs are valid, don't communicate. */
rv = APR_EINVAL;
}
"%d failures, %d from unknown logs",
}
}
return rv;
}
/* Enqueue data from server for off-line audit (cert, SCT(s))
* We already filtered out duplicate data being saved from this
* process. (With reverse proxy it will be the same data over
* and over.)
*/
#define SERVER_START 0x0001
#define KEY_START 0x0002
#define CERT_START 0x0003
#define SCT_START 0x0004
const char *key)
{
* subsequent error
*/
int i;
server_rec *s = c->base_server;
/* Any error in this function is a file I/O error;
* if such an error occurs, the audit file will be closed
* and removed, and this child won't be able to queue
* anything for audit. (It is likely that other child
* processes will have the same problem.)
*/
if (audit_file) { /* no error just occurred... */
audit_file_nonempty = 1;
if (rv == APR_SUCCESS) {
}
if (rv == APR_SUCCESS) {
}
if (rv == APR_SUCCESS) {
}
/* Write each certificate, starting with leaf */
int der_length;
/* now write the cert!!! */
if (rv == APR_SUCCESS) {
ap_assert(der_length > 0);
}
if (rv == APR_SUCCESS) {
}
}
/* Write each SCT */
if (rv == APR_SUCCESS) {
}
if (rv == APR_SUCCESS) {
}
}
if (rv != APR_SUCCESS) {
/* an I/O error occurred; file is not usable */
"Failed to write to %s, disabling audit for this "
"child", audit_fn_active);
audit_file = NULL;
/* not used in current implementations */
c->pool);
}
}
}
}
/* signed_certificate_timestamp */
static const unsigned short CT_EXTENSION_TYPE = 18;
/* See function of this name in openssl/apps/s_client.c */
{
const unsigned char *p;
int i, len;
if (!p) {
/* normal case */
"OCSP response callback called but no stapled response from server");
return 1;
}
if (!rsp) {
"Error parsing OCSP response");
return 0;
}
if (!br) {
"no OCSP basic response");
return 0;
}
int idx;
if (!single) {
continue;
}
if (idx == -1) {
continue;
}
"index of NID_ct_cert_scts: %d", idx);
}
return 1;
}
/* Callbacks and structures for handling custom TLS Extensions:
* cli_ext_first_cb - sends data for ClientHello TLS Extension
* cli_ext_second_cb - receives data from ServerHello TLS Extension
*/
const unsigned char **out,
void *arg)
{
/* nothing to send in ClientHello */
"client_extension_callback_1 called, "
"ext %hu will be in ClientHello",
ext_type);
return 1;
}
/* Get SCT(s) from ServerHello */
{
"client_extension_callback_2 called, "
"ext %hu was in ServerHello (len %hu)",
/* Note: Peer certificate is not available in this callback via
* SSL_get_peer_certificate(ssl)
*/
return 1;
}
/* See SSLClient::VerifyCallback() in c-t/src/client/ssl_client.cc
* (That's a beast and hard to duplicate in depth when you consider
* all the support classes it relies on; mod_ssl_ct needs to be a
* C++ module so that the bugs are fixed in one place.)
*
* . This code should care about stapled SCTs but doesn't.
* . This code, unlike SSLClient::VerifyCallback(), doesn't look
* at the OpenSSL "input" chain.
*/
{
apr_pool_t *p = c->pool;
int extension_index;
return OK;
}
"ssl_ct_ssl_proxy_verify() - get server certificate info");
if (chain_size < 1) {
"odd chain size %d -- cannot proceed", chain_size);
return APR_EINVAL;
}
/* Note: SSLClient::Verify looks in both the input chain and the
* verified chain.
*/
-1);
/* use X509_get_ext(certs->leaf, extension_index) to obtain X509_EXTENSION * */
if (extension_index >= 0) {
void *ext_struct;
/* as in Cert::ExtensionStructure() */
NULL, /* ignore criticality of extension */
NULL); /* UNDOC */
if (ext_struct == NULL) {
"Could not retrieve SCT list from certificate (unexpected)");
}
else {
/* as in Cert::OctetStringExtensionData */
}
}
return OK;
}
{
apr_pool_t *p = c->pool;
const char *key;
server_rec *s = c->base_server;
int validation_error = 0, missing_sct_error = 0;
return OK;
}
ssl_ct_ssl_proxy_verify(s, c, chain);
"finally at the point where we can see where SCTs came from"
" %pp/%pp/%pp (c %pp)",
conncfg->ocsp_sct_list, c);
/* At this point we have the SCTs from the cert (if any) and the
* SCTs from the TLS extension (if any) in ct_conn_config.
*/
|| conncfg->ocsp_sct_list) {
/* The key is critical to avoiding validating and queueing of
* the same stuff over and over.
*
* Is there any cheaper check than server cert and SCTs all exactly
* the same as before?
*/
"key for server data: %s", key);
if (!cached) {
if (rv != APR_SUCCESS) {
validation_error = 1;
}
/* some other thread snuck in
* we assume that the other thread got the same validation
* result that we did
*/
}
else {
/* no other thread snuck in */
}
}
}
else {
/* cached */
if (rv != APR_SUCCESS) {
validation_error = 1;
}
}
}
else {
/* No SCTs at all; consult configuration to know what to do. */
missing_sct_error = 1;
}
}
"SCT list received in: %s%s%s(%s) (c %pp)",
c);
if (missing_sct_error || validation_error) {
"Forbidding access to backend server; no valid SCTs");
return HTTP_FORBIDDEN;
}
}
return OK;
}
const unsigned char *in,
void *arg)
{
/* this callback tells us that client is CT-aware;
* there's nothing of interest in the extension data
*/
"server_extension_callback_1 called, "
"ext %hu was in ClientHello (len %hu)",
return 1;
}
const unsigned char **out,
void *arg)
{
const char *fingerprint;
const unsigned char *scts;
if (!is_client_ct_aware(c)) {
/* Hmmm... Is this actually called if the client doesn't include
* the extension in the ClientHello? I don't think so.
*/
"server_extension_callback_2: client isn't CT-aware");
/* Skip this extension for ServerHello */
return -1;
}
/* need to reply with SCT */
"server_extension_callback_2 called, "
"ext %hu will be in ServerHello",
ext_type);
if (rv == APR_SUCCESS) {
}
else {
/* Skip this extension for ServerHello */
return -1;
}
return 1;
}
void *arg)
{
if (type == CT_EXTENSION_TYPE) {
"tlsext_cb called, got CT TLS extension");
}
}
{
/* This callback is needed only to determine that the peer is CT-aware
* when resuming a session. For an initial handshake, the callbacks
* registered via SSL_CTX_set_custom_srv_ext() are sufficient.
*/
return OK;
}
{
cbi->s = s;
/* _cli_ = "client" */
"Unable to initalize Certificate Transparency client "
"extension callbacks (callback for %d already registered?)",
return HTTP_INTERNAL_SERVER_ERROR;
}
/* Uhh, hopefully this doesn't collide with anybody else. mod_ssl
* currently only sets this on the server SSL_CTX, when OCSP is
* enabled.
*/
}
else if (!is_proxy) {
/* _srv_ = "server" */
"Unable to initalize Certificate Transparency server "
"extension callback (callbacks for %d already registered?)",
return HTTP_INTERNAL_SERVER_ERROR;
}
}
return OK;
}
static int ssl_ct_post_read_request(request_rec *r)
{
}
else {
}
return DECLINED;
}
{
APR_LOCK_DEFAULT, 0);
if (rv != APR_SUCCESS) {
return rv;
}
return OK;
}
{
server_rec *s = data;
if (!audit_file) { /* something bad happened after child init */
return APR_SUCCESS;
}
/* the normal cleanup was disabled in the call to apr_file_open */
audit_file = NULL;
if (rv == APR_SUCCESS) {
if (audit_file_nonempty) {
/* not used in current implementations */
}
else {
/* No data written; just remove the file */
/* not used in current implementations */
}
}
if (rv != APR_SUCCESS) {
}
return APR_SUCCESS; /* what, you think anybody cares? */
}
{
const char *audit_basename;
if (rv != APR_SUCCESS) {
"could not initialize " SSL_CT_MUTEX_TYPE
" mutex in child");
/* might crash otherwise due to lack of checking for initialized data
* in all the right places, but this is going to skip pchild cleanup
*/
}
if (rv != APR_SUCCESS) {
"could not create rwlock in child");
}
if (rv != APR_SUCCESS) {
/* might crash otherwise due to lack of checking for initialized data
* in all the right places, but this is going to skip pchild cleanup
*/
}
p);
if (rv != APR_SUCCESS) {
"could not allocate a thread mutex");
/* might crash otherwise due to lack of checking for initialized data
* in all the right places, but this is going to skip pchild cleanup
*/
}
}
if (rv != APR_SUCCESS) {
"could not allocate a thread mutex");
/* might crash otherwise due to lack of checking for initialized data
* in all the right places, but this is going to skip pchild cleanup
*/
}
getpid());
audit_basename, p, s);
if (rv != APR_SUCCESS) {
/* might crash otherwise due to lack of checking for initialized data
* in all the right places, but this is going to skip pchild cleanup
*/
}
if (ctutil_file_exists(p, audit_fn_active)) {
"ummm, pid-specific file %s was reused before audit grabbed it! (removing)",
}
if (ctutil_file_exists(p, audit_fn_perm)) {
"ummm, pid-specific file %s was reused before audit grabbed it! (removing)",
}
APR_FPROT_OS_DEFAULT, p);
if (rv != APR_SUCCESS) {
"can't create %s", audit_fn_active);
audit_file = NULL;
}
if (audit_file) {
}
} /* !PROXY_OBLIVIOUS */
}
{
return conf;
}
{
: base->proxy_awareness;
return conf;
}
static int ssl_ct_detach_backend(request_rec *r,
{
if (origin) {
"ssl_ct_detach_backend, %d%d%d",
NULL);
if (*list) {
if (*last == ',') {
*last = '\0';
}
}
}
else {
"No backend connection available in "
"ssl_ct_detach_backend(); assuming peer unaware");
}
return OK;
}
static void ct_register_hooks(apr_pool_t *p)
{
}
static const char *parse_num(apr_pool_t *p,
const char *cmd_name)
{
char *endptr;
errno = 0;
if (errno != 0
|| *endptr != '\0'
return apr_psprintf(p, "%s must be between %ld "
}
return NULL;
}
{
if (err) {
return err;
}
" does not exist", NULL);
}
return NULL;
}
{
if (err) {
return err;
}
return NULL;
}
{
long val;
if (err) {
return err;
}
if (err) {
return err;
}
return NULL;
}
{
}
}
}
else {
}
return NULL;
}
{
if (err) {
return err;
}
" does not exist", NULL);
}
return NULL;
}
{
long val;
if (err) {
return err;
}
"CTServerHelloSCTLimit");
if (err) {
return err;
}
return NULL;
}
char *const argv[])
{
*max_valid_time, *url;
int cur_arg;
if (err) {
return err;
}
if (argc != 6) {
return "CTStaticLogConfig: 6 arguments are required";
}
cur_arg = 0;
}
public_key = NULL;
}
else {
}
distrusted = NULL;
}
}
}
}
if (!sconf->static_log_config) {
}
if (rv != APR_SUCCESS) {
return "Error processing static log configuration";
}
return NULL;
}
const char *sct_dn)
{
const char *fingerprint;
if (err) {
return err;
}
if (rv != APR_SUCCESS) {
return apr_psprintf(p, "could not open certificate file %s (%pm)",
}
if (!cert) {
return apr_psprintf(p, "could not read certificate from file %s",
cert_fn);
}
if (!ctutil_dir_exists(p, sct_dn)) {
" does not exist", NULL);
}
return NULL;
}
{
if (err) {
return err;
}
}
}
"CTLogClient: File ",
arg,
" does not exist",
NULL);
}
return NULL;
}
static const command_rec ct_cmds[] =
{
RSRC_CONF, /* GLOBAL_ONLY - audit data spans servers */
"Location to store files of audit data"),
RSRC_CONF, /* GLOBAL_ONLY - otherwise, you couldn't share
* the same SCT list for a cert used by two
* different vhosts (and the SCT maintenance daemon
* would be more complex)
*/
"Log configuration database"),
RSRC_CONF, /* GLOBAL_ONLY - otherwise, you couldn't share
* the same SCT list for a cert used by two
* different vhosts
*/
"Max age of SCT obtained from log before refresh"),
RSRC_CONF, /* per-server */
"\"oblivious\" to neither ask for nor check SCTs, "
"\"aware\" to ask for and process SCTs but allow all connections, "
"or \"require\" to abort backend connections if an acceptable "
"SCT is not provided"),
RSRC_CONF, /* GLOBAL_ONLY - otherwise, you couldn't share
* the same SCT list for a cert used by two
* different vhosts
*/
"Limit on number of SCTs sent in ServerHello"),
RSRC_CONF, /* GLOBAL_ONLY - otherwise, you couldn't share
* the same SCT list for a cert used by two
* different vhosts (and the SCT maintenance daemon
* would be more complex)
*/
"Location to store SCTs obtained from logs"),
RSRC_CONF, /* GLOBAL_ONLY */
"Static log configuration record"),
RSRC_CONF, /* GLOBAL_ONLY - otherwise, you couldn't share
* the same SCT list for a cert used by two
* different vhosts (and the SCT maintenance daemon
* would be more complex)
*/
"Point to directory with static SCTs corresponding to the "
"specified certificate"),
RSRC_CONF, /* GLOBAL_ONLY - otherwise, you couldn't share
* the same SCTs for a cert used by two
* different vhosts (and it would be just plain
* silly :) )
*/
"Location of certificate-transparency.org (or compatible) log client tool"),
{NULL}
};
{
NULL,
NULL,
};