| /* 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 "ssl_ct_sct.h" |
| #include "ssl_ct_util.h" |
| |
| #include "http_log.h" |
| |
| APLOG_USE_MODULE(ssl_ct); |
| |
| static apr_status_t verify_signature(sct_fields_t *sctf, |
| EVP_PKEY *pkey) |
| { |
| EVP_MD_CTX *ctx; |
| int rc; |
| |
| if (sctf->signed_data == NULL) { |
| return APR_EINVAL; |
| } |
| |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| ctx = EVP_MD_CTX_create(); |
| #else |
| ctx = EVP_MD_CTX_new(); |
| #endif |
| ap_assert(1 == EVP_VerifyInit(ctx, EVP_sha256())); |
| ap_assert(1 == EVP_VerifyUpdate(ctx, sctf->signed_data, |
| sctf->signed_data_len)); |
| rc = EVP_VerifyFinal(ctx, sctf->sig, sctf->siglen, pkey); |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L |
| EVP_MD_CTX_destroy(ctx); |
| #else |
| EVP_MD_CTX_free(ctx); |
| #endif |
| |
| return rc == 1 ? APR_SUCCESS : APR_EINVAL; |
| } |
| |
| apr_status_t sct_verify_signature(conn_rec *c, sct_fields_t *sctf, |
| apr_array_header_t *log_config) |
| { |
| apr_status_t rv = APR_EINVAL; |
| int i; |
| ct_log_config **config_elts; |
| int nelts = log_config->nelts; |
| |
| ap_assert(sctf->signed_data != NULL); |
| |
| config_elts = (ct_log_config **)log_config->elts; |
| |
| for (i = 0; i < nelts; i++) { |
| EVP_PKEY *pubkey = config_elts[i]->public_key; |
| const char *logid = config_elts[i]->log_id; |
| |
| if (!pubkey || !logid) { |
| continue; |
| } |
| |
| if (!memcmp(logid, sctf->logid, LOG_ID_SIZE)) { |
| if (!log_valid_for_received_sct(config_elts[i], sctf->time)) { |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, |
| APLOGNO(02766) "Got SCT from distrusted log, or " |
| "out of trusted time interval"); |
| return APR_EINVAL; |
| } |
| rv = verify_signature(sctf, pubkey); |
| if (rv != APR_SUCCESS) { |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(02767) |
| "verify_signature failed"); |
| } |
| else { |
| ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03037) |
| "verify_signature succeeded"); |
| } |
| return rv; |
| } |
| } |
| |
| return APR_NOTFOUND; |
| } |
| |
| apr_status_t sct_parse(const char *source, |
| server_rec *s, const unsigned char *sct, |
| apr_size_t len, cert_chain *cc, |
| sct_fields_t *fields) |
| { |
| const unsigned char *cur; |
| apr_size_t orig_len = len; |
| apr_status_t rv; |
| |
| memset(fields, 0, sizeof *fields); |
| |
| if (len < 1 + LOG_ID_SIZE + 8) { |
| /* no room for header */ |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, |
| APLOGNO(02768) "SCT size %" APR_SIZE_T_FMT " is too small", |
| len); |
| return APR_EINVAL; |
| } |
| |
| cur = sct; |
| |
| fields->version = *cur; |
| cur++; |
| len -= 1; |
| memcpy(fields->logid, cur, LOG_ID_SIZE); |
| cur += LOG_ID_SIZE; |
| len -= LOG_ID_SIZE; |
| rv = ctutil_deserialize_uint64(&cur, &len, &fields->timestamp); |
| ap_assert(rv == APR_SUCCESS); |
| |
| fields->time = apr_time_from_msec(fields->timestamp); |
| |
| /* XXX maybe do this only if log level is such that we'll |
| * use it later? |
| */ |
| apr_rfc822_date(fields->timestr, fields->time); |
| |
| |
| if (len < 2) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, |
| APLOGNO(02769) "SCT size %" APR_SIZE_T_FMT " has no space " |
| "for extension len", orig_len); |
| return APR_EINVAL; |
| } |
| |
| rv = ctutil_deserialize_uint16(&cur, &len, &fields->extlen); |
| ap_assert(rv == APR_SUCCESS); |
| |
| if (fields->extlen != 0) { |
| if (fields->extlen < len) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, |
| APLOGNO(02770) "SCT size %" APR_SIZE_T_FMT " has no " |
| "space for %hu bytes of extensions", |
| orig_len, fields->extlen); |
| return APR_EINVAL; |
| } |
| |
| fields->extensions = cur; |
| cur += fields->extlen; |
| len -= fields->extlen; |
| } |
| else { |
| fields->extensions = 0; |
| } |
| |
| if (len < 4) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, |
| APLOGNO(02771) "SCT size %" APR_SIZE_T_FMT " has no space " |
| "for hash algorithm, signature algorithm, and " |
| "signature len", |
| orig_len); |
| return APR_EINVAL; |
| } |
| |
| fields->hash_alg = *cur; |
| cur += 1; |
| len -= 1; |
| fields->sig_alg = *cur; |
| cur += 1; |
| len -= 1; |
| rv = ctutil_deserialize_uint16(&cur, &len, &fields->siglen); |
| ap_assert(rv == APR_SUCCESS); |
| |
| if (fields->siglen < len) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, |
| APLOGNO(02772) "SCT has no space for signature"); |
| return APR_EINVAL; |
| } |
| |
| fields->sig = cur; |
| cur += fields->siglen; |
| len -= fields->siglen; |
| |
| if (cc) { |
| /* If we have the server certificate, we can construct the |
| * data over which the signature is computed. |
| */ |
| |
| /* XXX Which part is signed? */ |
| /* See certificate-transparency/src/proto/serializer.cc, |
| * method Serializer::SerializeV1CertSCTSignatureInput() |
| */ |
| |
| apr_size_t orig_len; |
| apr_size_t avail; |
| int der_length; |
| unsigned char *mem; |
| unsigned char *orig_mem = NULL; |
| |
| der_length = i2d_X509(cc->leaf, NULL); |
| if (der_length < 0) { |
| rv = APR_EINVAL; |
| } |
| |
| if (rv == APR_SUCCESS) { |
| orig_len = 0 |
| + 1 /* version 1 */ |
| + 1 /* CERTIFICATE_TIMESTAMP */ |
| + 8 /* timestamp */ |
| + 2 /* X509_ENTRY */ |
| + 3 + der_length /* 24-bit length + X509 */ |
| + 2 + fields->extlen /* 16-bit length + extensions */ |
| ; |
| avail = orig_len; |
| mem = malloc(avail); |
| orig_mem = mem; |
| |
| rv = ctutil_serialize_uint8(&mem, &avail, 0); /* version 1 */ |
| if (rv == APR_SUCCESS) { |
| rv = ctutil_serialize_uint8(&mem, &avail, 0); /* CERTIFICATE_TIMESTAMP */ |
| } |
| if (rv == APR_SUCCESS) { |
| rv = ctutil_serialize_uint64(&mem, &avail, fields->timestamp); |
| } |
| if (rv == APR_SUCCESS) { |
| rv = ctutil_serialize_uint16(&mem, &avail, 0); /* X509_ENTRY */ |
| } |
| if (rv == APR_SUCCESS) { |
| /* Get DER encoding of leaf certificate */ |
| unsigned char *der_buf |
| /* get OpenSSL to allocate: */ |
| = NULL; |
| |
| der_length = i2d_X509(cc->leaf, &der_buf); |
| if (der_length < 0) { |
| rv = APR_EINVAL; |
| } |
| else { |
| rv = ctutil_write_var24_bytes(&mem, &avail, |
| der_buf, der_length); |
| OPENSSL_free(der_buf); |
| } |
| } |
| if (rv == APR_SUCCESS) { |
| rv = ctutil_write_var16_bytes(&mem, &avail, fields->extensions, |
| fields->extlen); |
| } |
| } |
| |
| if (rv != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, |
| APLOGNO(02773) "Failed to reconstruct signed data for " |
| "SCT"); |
| if (orig_mem != NULL) { |
| free(orig_mem); |
| } |
| } |
| else { |
| if (avail != 0) { |
| ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, |
| APLOGNO(02774) "length miscalculation for signed " |
| "data (%" APR_SIZE_T_FMT |
| " vs. %" APR_SIZE_T_FMT ")", |
| orig_len, avail); |
| } |
| fields->signed_data_len = orig_len - avail; |
| fields->signed_data = orig_mem; |
| /* Force invalid signature error: orig_mem[0] = orig_mem[0] + 1; */ |
| } |
| } |
| |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03038) |
| "SCT from %s: version %d timestamp %s hash alg %d sig alg %d", |
| source, fields->version, fields->timestr, |
| fields->hash_alg, fields->sig_alg); |
| ap_log_data(APLOG_MARK, APLOG_DEBUG, s, "Log Id", |
| fields->logid, sizeof(fields->logid), |
| AP_LOG_DATA_SHOW_OFFSET); |
| ap_log_data(APLOG_MARK, APLOG_DEBUG, s, "Signature", |
| fields->sig, fields->siglen, |
| AP_LOG_DATA_SHOW_OFFSET); |
| |
| ap_assert(!(fields->signed_data && rv != APR_SUCCESS)); |
| |
| return rv; |
| } |
| |
| void sct_release(sct_fields_t *sctf) |
| { |
| if (sctf->signed_data) { |
| free((void *)sctf->signed_data); |
| sctf->signed_data = NULL; |
| } |
| } |
| |
| apr_status_t sct_verify_timestamp(conn_rec *c, sct_fields_t *sctf) |
| { |
| if (sctf->time > apr_time_now()) { |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, |
| APLOGNO(02775) "Server sent SCT not yet valid (timestamp " |
| "%s)", |
| sctf->timestr); |
| return APR_EINVAL; |
| } |
| return APR_SUCCESS; |
| } |