| /* 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 || defined(LIBRESSL_VERSION_NUMBER) | |
| 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 || defined(LIBRESSL_VERSION_NUMBER) | |
| 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 (len < fields->extlen) { | |
| 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 (len < fields->siglen) { | |
| 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 = ap_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; | |
| } |