/* 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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. */ #include "apr_dbd.h" #include "apr_escape.h" #include "apr_strings.h" #include "httpd.h" #include "http_log.h" #include "http_main.h" #include "ssl_ct_sct.h" #include "ssl_ct_log_config.h" #include "ssl_ct_util.h" APLOG_USE_MODULE(ssl_ct); int log_config_readable(apr_pool_t *p, const char *logconfig, const char **msg) { const apr_dbd_driver_t *driver; apr_dbd_t *handle; apr_status_t rv; apr_dbd_results_t *res; int rc; rv = apr_dbd_get_driver(p, "sqlite3", &driver); if (rv != APR_SUCCESS) { if (msg) { *msg = "SQLite3 driver cannot be loaded"; } return 0; } rv = apr_dbd_open(driver, p, logconfig, &handle); if (rv != APR_SUCCESS) { return 0; } /* is there a cheaper way? */ res = NULL; rc = apr_dbd_select(driver, p, handle, &res, "SELECT * FROM loginfo WHERE id = 0", 0); apr_dbd_close(driver, handle); if (rc != 0) { return 0; } return 1; } static apr_status_t public_key_cleanup(void *data) { EVP_PKEY *pubkey = data; EVP_PKEY_free(pubkey); return APR_SUCCESS; } static apr_status_t read_public_key(apr_pool_t *p, const char *pubkey_fname, EVP_PKEY **ppkey) { apr_status_t rv; EVP_PKEY *pubkey; FILE *pubkeyf; *ppkey = NULL; rv = ctutil_fopen(pubkey_fname, "r", &pubkeyf); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(02751) "could not open log public key file %s", pubkey_fname); return rv; } pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL); if (!pubkey) { fclose(pubkeyf); rv = APR_EINVAL; ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02752) "PEM_read_PUBKEY() failed to process " "public key file %s", pubkey_fname); return rv; } fclose(pubkeyf); *ppkey = pubkey; apr_pool_cleanup_register(p, (void *)pubkey, public_key_cleanup, apr_pool_cleanup_null); return APR_SUCCESS; } static void digest_public_key(EVP_PKEY *pubkey, unsigned char digest[LOG_ID_SIZE]) { int len = i2d_PUBKEY(pubkey, NULL); unsigned char *val = ap_malloc(len); unsigned char *tmp = val; ap_assert(LOG_ID_SIZE == SHA256_DIGEST_LENGTH); i2d_PUBKEY(pubkey, &tmp); #if OPENSSL_VERSION_NUMBER < 0x30000000 { SHA256_CTX sha256ctx; SHA256_Init(&sha256ctx); SHA256_Update(&sha256ctx, (unsigned char *)val, len); SHA256_Final(digest, &sha256ctx); } #else { EVP_MD_CTX *md_ctx; unsigned int dlen = 0; md_ctx = EVP_MD_CTX_create(); ap_assert(md_ctx != NULL); ap_assert(EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL)); ap_assert(EVP_DigestUpdate(md_ctx, val, len)); ap_assert(EVP_DigestFinal_ex(md_ctx, digest, &dlen)); ap_assert(dlen == SHA256_DIGEST_LENGTH); EVP_MD_CTX_destroy(md_ctx); } #endif free(val); } static apr_status_t parse_log_url(apr_pool_t *p, const char *lu, apr_uri_t *puri) { apr_status_t rv; apr_uri_t uri; rv = apr_uri_parse(p, lu, &uri); if (rv == APR_SUCCESS) { if (!uri.scheme || !uri.hostname || !uri.path) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02753) "Error in log url \"%s\": URL can't be " "parsed or is missing required elements", lu); rv = APR_EINVAL; } if (strcmp(uri.scheme, "http")) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02754) "Error in log url \"%s\": Only scheme " "\"http\" (instead of \"%s\") is currently " "accepted", lu, uri.scheme); rv = APR_EINVAL; } if (strcmp(uri.path, "/")) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02755) "Error in log url \"%s\": Only path " "\"/\" (instead of \"%s\") is currently accepted", lu, uri.path); rv = APR_EINVAL; } } if (rv == APR_SUCCESS) { *puri = uri; } return rv; } static apr_status_t parse_time_str(apr_pool_t *p, const char *time_str, apr_time_t *time) { apr_int64_t val; const char *end; errno = 0; val = apr_strtoi64(time_str, (char **)&end, 10); if (errno || *end != '\0') { return APR_EINVAL; } *time = apr_time_from_msec(val); return APR_SUCCESS; } /* The log_config array should have already been allocated from p. */ apr_status_t save_log_config_entry(apr_array_header_t *log_config, apr_pool_t *p, const char *log_id, const char *pubkey_fname, const char *distrusted_str, const char *min_time_str, const char *max_time_str, const char *url) { apr_size_t len; apr_status_t rv; apr_time_t min_time, max_time; apr_uri_t uri; char *computed_log_id = NULL, *log_id_bin = NULL; ct_log_config *newconf, **pnewconf; int distrusted; EVP_PKEY *public_key; if (!distrusted_str) { distrusted = DISTRUSTED_UNSET; } else if (!strcmp(distrusted_str, "1")) { distrusted = DISTRUSTED; } else if (!strcmp(distrusted_str, "0")) { distrusted = TRUSTED; } else { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02756) "Trusted status \"%s\" not valid", distrusted_str); return APR_EINVAL; } if (log_id) { rv = apr_unescape_hex(NULL, log_id, strlen(log_id), 0, &len); if (rv != 0 || len != 32) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02757) "Log id \"%s\" not valid", log_id); log_id_bin = apr_palloc(p, len); apr_unescape_hex(log_id_bin, log_id, strlen(log_id), 0, NULL); } } if (pubkey_fname) { rv = read_public_key(p, pubkey_fname, &public_key); if (rv != APR_SUCCESS) { return rv; } } else { public_key = NULL; } if (min_time_str) { rv = parse_time_str(p, min_time_str, &min_time); if (rv) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02758) "Invalid min time \"%s\"", min_time_str); return rv; } } else { min_time = 0; } if (max_time_str) { rv = parse_time_str(p, max_time_str, &max_time); if (rv) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02759) "Invalid max time \"%s\"", max_time_str); return rv; } } else { max_time = 0; } if (url) { rv = parse_log_url(p, url, &uri); if (rv != APR_SUCCESS) { return rv; } } newconf = apr_pcalloc(p, sizeof(ct_log_config)); pnewconf = (ct_log_config **)apr_array_push(log_config); *pnewconf = newconf; newconf->distrusted = distrusted; newconf->public_key = public_key; if (newconf->public_key) { computed_log_id = apr_palloc(p, LOG_ID_SIZE); digest_public_key(newconf->public_key, (unsigned char *)computed_log_id); } if (computed_log_id && log_id_bin) { if (memcmp(computed_log_id, log_id_bin, LOG_ID_SIZE)) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02760) "Provided log id doesn't match digest " "of public key"); return APR_EINVAL; } } newconf->log_id = log_id_bin ? log_id_bin : computed_log_id; newconf->min_valid_time = min_time; newconf->max_valid_time = max_time; newconf->url = url; if (url) { newconf->uri = uri; newconf->uri_str = apr_uri_unparse(p, &uri, 0); } newconf->public_key_pem = pubkey_fname; return APR_SUCCESS; } apr_status_t read_config_db(apr_pool_t *p, server_rec *s_main, const char *log_config_fname, apr_array_header_t *log_config) { apr_status_t rv; const apr_dbd_driver_t *driver; apr_dbd_t *handle; apr_dbd_results_t *res; apr_dbd_row_t *row; int rc; ap_assert(log_config); rv = apr_dbd_get_driver(p, "sqlite3", &driver); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s_main, APLOGNO(02761) "APR SQLite3 driver can't be loaded"); return rv; } rv = apr_dbd_open(driver, p, log_config_fname, &handle); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s_main, APLOGNO(02762) "Can't open SQLite3 db %s", log_config_fname); return rv; } res = NULL; rc = apr_dbd_select(driver, p, handle, &res, "SELECT * FROM loginfo", 0); if (rc != 0) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s_main, APLOGNO(02763) "SELECT of loginfo records failed"); apr_dbd_close(driver, handle); return APR_EINVAL; } rc = apr_dbd_num_tuples(driver, res); switch (rc) { case -1: ap_log_error(APLOG_MARK, APLOG_ERR, 0, s_main, APLOGNO(02764) "Unexpected asynchronous result reading %s", log_config_fname); apr_dbd_close(driver, handle); return APR_EINVAL; case 0: ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s_main, APLOGNO(02765) "Log configuration in %s is empty", log_config_fname); apr_dbd_close(driver, handle); return APR_SUCCESS; default: /* quiet some lints */ break; } for (rv = apr_dbd_get_row(driver, p, res, &row, -1); rv == APR_SUCCESS; rv = apr_dbd_get_row(driver, p, res, &row, -1)) { int cur = 0; const char *id = apr_dbd_get_entry(driver, row, cur++); const char *log_id = apr_dbd_get_entry(driver, row, cur++); const char *public_key = apr_dbd_get_entry(driver, row, cur++); const char *distrusted = apr_dbd_get_entry(driver, row, cur++); const char *min_timestamp = apr_dbd_get_entry(driver, row, cur++); const char *max_timestamp = apr_dbd_get_entry(driver, row, cur++); const char *url = apr_dbd_get_entry(driver, row, cur++); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s_main, APLOGNO(03036) "Log config: Record %s, log id %s, public key file %s," " distrusted %s, URL %s, time %s->%s", id, log_id ? log_id : "(unset)", public_key ? public_key : "(unset)", distrusted ? distrusted : "(unset, defaults to trusted)", url ? url : "(unset)", min_timestamp ? min_timestamp : "-INF", max_timestamp ? max_timestamp : "+INF"); rv = save_log_config_entry(log_config, p, log_id, public_key, distrusted, min_timestamp, max_timestamp, url); if (rv != APR_SUCCESS) { apr_dbd_close(driver, handle); return rv; } } apr_dbd_close(driver, handle); return APR_SUCCESS; } int log_valid_for_received_sct(const ct_log_config *l, apr_time_t to_check) { if (l->distrusted == DISTRUSTED) { return 0; } if (l->max_valid_time && l->max_valid_time < to_check) { return 0; } if (l->min_valid_time && l->min_valid_time < to_check) { return 0; } return 1; } int log_valid_for_sent_sct(const ct_log_config *l) { /* The log could return us an SCT with an older timestamp which * is within the trusted time interval for the log, but for * simplicity let's just assume that if the log isn't still * within a trusted interval we won't send SCTs from the log. */ return log_valid_for_received_sct(l, apr_time_now()); } int log_configured_for_fetching_sct(const ct_log_config *l) { /* must have a url and a public key configured in order to obtain * an SCT from the log */ return l->url != NULL && l->public_key != NULL; }