| /* 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 = malloc(len); |
| unsigned char *tmp = val; |
| SHA256_CTX sha256ctx; |
| |
| ap_assert(LOG_ID_SIZE == SHA256_DIGEST_LENGTH); |
| |
| i2d_PUBKEY(pubkey, &tmp); |
| SHA256_Init(&sha256ctx); |
| SHA256_Update(&sha256ctx, (unsigned char *)val, len); |
| SHA256_Final(digest, &sha256ctx); |
| 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; |
| } |