| /** @file |
| |
| A brief file description |
| |
| @section license License |
| |
| 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 "tscore/ink_config.h" |
| #include "tscore/EventNotify.h" |
| #include "tscore/I_Layout.h" |
| #include "tscore/TSSystemState.h" |
| |
| #include "InkAPIInternal.h" // Added to include the ssl_hook definitions |
| #include "Log.h" |
| #include "HttpTunnel.h" |
| #include "ProxyProtocol.h" |
| #include "HttpConfig.h" |
| |
| #include "P_Net.h" |
| #include "P_SSLUtils.h" |
| #include "P_SSLNextProtocolSet.h" |
| #include "P_SSLConfig.h" |
| #include "P_SSLClientUtils.h" |
| #include "P_SSLSNI.h" |
| #include "BIO_fastopen.h" |
| #include "SSLStats.h" |
| #include "SSLInternal.h" |
| #include "P_ALPNSupport.h" |
| |
| #include <climits> |
| #include <string> |
| #include <cstring> |
| |
| using namespace std::literals; |
| |
| #if TS_USE_TLS_ASYNC |
| #include <openssl/async.h> |
| #endif |
| |
| // This is missing from BoringSSL |
| #ifndef BIO_eof |
| #define BIO_eof(b) (int)BIO_ctrl(b, BIO_CTRL_EOF, 0, nullptr) |
| #endif |
| |
| #define SSL_READ_ERROR_NONE 0 |
| #define SSL_READ_ERROR 1 |
| #define SSL_READ_READY 2 |
| #define SSL_READ_COMPLETE 3 |
| #define SSL_READ_WOULD_BLOCK 4 |
| #define SSL_READ_EOS 5 |
| #define SSL_HANDSHAKE_WANT_READ 6 |
| #define SSL_HANDSHAKE_WANT_WRITE 7 |
| #define SSL_HANDSHAKE_WANT_ACCEPT 8 |
| #define SSL_HANDSHAKE_WANT_CONNECT 9 |
| #define SSL_WRITE_WOULD_BLOCK 10 |
| #define SSL_WAIT_FOR_HOOK 11 |
| #define SSL_WAIT_FOR_ASYNC 12 |
| |
| ClassAllocator<SSLNetVConnection> sslNetVCAllocator("sslNetVCAllocator"); |
| |
| namespace |
| { |
| /// Callback to get two locks. |
| /// The lock for this continuation, and for the target continuation. |
| class ContWrapper : public Continuation |
| { |
| public: |
| /** Constructor. |
| This takes the secondary @a mutex and the @a target continuation |
| to invoke, along with the arguments for that invocation. |
| */ |
| ContWrapper(ProxyMutex *mutex, ///< Mutex for this continuation (primary lock). |
| Continuation *target, ///< "Real" continuation we want to call. |
| int eventId = EVENT_IMMEDIATE, ///< Event ID for invocation of @a target. |
| void *edata = nullptr ///< Data for invocation of @a target. |
| ) |
| : Continuation(mutex), _target(target), _eventId(eventId), _edata(edata) |
| { |
| SET_HANDLER(&ContWrapper::event_handler); |
| } |
| |
| /// Required event handler method. |
| int |
| event_handler(int, void *) |
| { |
| EThread *eth = this_ethread(); |
| |
| MUTEX_TRY_LOCK(lock, _target->mutex, eth); |
| if (lock.is_locked()) { // got the target lock, we can proceed. |
| _target->handleEvent(_eventId, _edata); |
| delete this; |
| } else { // can't get both locks, try again. |
| eventProcessor.schedule_imm(this, ET_NET); |
| } |
| return 0; |
| } |
| |
| /** Convenience static method. |
| |
| This lets a client make one call and not have to (accurately) |
| copy the invocation logic embedded here. We duplicate it near |
| by textually so it is easier to keep in sync. |
| |
| This takes the same arguments as the constructor but, if the |
| lock can be obtained immediately, does not construct an |
| instance but simply calls the @a target. |
| */ |
| static void |
| wrap(ProxyMutex *mutex, ///< Mutex for this continuation (primary lock). |
| Continuation *target, ///< "Real" continuation we want to call. |
| int eventId = EVENT_IMMEDIATE, ///< Event ID for invocation of @a target. |
| void *edata = nullptr ///< Data for invocation of @a target. |
| ) |
| { |
| EThread *eth = this_ethread(); |
| if (!target->mutex) { |
| // If there's no mutex, plugin doesn't care about locking so why should we? |
| target->handleEvent(eventId, edata); |
| } else { |
| MUTEX_TRY_LOCK(lock, target->mutex, eth); |
| if (lock.is_locked()) { |
| target->handleEvent(eventId, edata); |
| } else { |
| eventProcessor.schedule_imm(new ContWrapper(mutex, target, eventId, edata), ET_NET); |
| } |
| } |
| } |
| |
| private: |
| Continuation *_target; ///< Continuation to invoke. |
| int _eventId; ///< with this event |
| void *_edata; ///< and this data |
| }; |
| } // namespace |
| |
| // |
| // Private |
| // |
| |
| void |
| SSLNetVConnection::_make_ssl_connection(SSL_CTX *ctx) |
| { |
| if (likely(this->ssl = SSL_new(ctx))) { |
| // Only set up the bio stuff for the server side |
| if (this->get_context() == NET_VCONNECTION_OUT) { |
| BIO *bio = BIO_new(const_cast<BIO_METHOD *>(BIO_s_fastopen())); |
| BIO_set_fd(bio, this->get_socket(), BIO_NOCLOSE); |
| |
| if (this->options.f_tcp_fastopen) { |
| BIO_set_conn_address(bio, this->get_remote_addr()); |
| } |
| |
| SSL_set_bio(ssl, bio, bio); |
| } else { |
| this->initialize_handshake_buffers(); |
| BIO *rbio = BIO_new(BIO_s_mem()); |
| BIO *wbio = BIO_new_fd(this->get_socket(), BIO_NOCLOSE); |
| BIO_set_mem_eof_return(wbio, -1); |
| SSL_set_bio(ssl, rbio, wbio); |
| |
| #if TS_HAS_TLS_EARLY_DATA |
| // Must disable OpenSSL's internal anti-replay if external cache is used with |
| // 0-rtt, otherwise session reuse will be broken. The freshness check described |
| // in https://tools.ietf.org/html/rfc8446#section-8.3 is still performed. But we |
| // still need to implement something to try to prevent replay atacks. |
| // |
| // We are now also disabling this when using OpenSSL's internal cache, since we |
| // are calling "ssl_accept" non-blocking, it seems to be confusing the anti-replay |
| // mechanism and causing session resumption to fail. |
| SSLConfig::scoped_config params; |
| if (SSL_version(ssl) >= TLS1_3_VERSION && params->server_max_early_data > 0) { |
| bool ret1 = false; |
| bool ret2 = false; |
| if ((ret1 = SSL_set_max_early_data(ssl, params->server_max_early_data)) == 1) { |
| Debug("ssl_early_data", "SSL_set_max_early_data: success"); |
| } else { |
| Debug("ssl_early_data", "SSL_set_max_early_data: failed"); |
| } |
| |
| if ((ret2 = SSL_set_recv_max_early_data(ssl, params->server_recv_max_early_data)) == 1) { |
| Debug("ssl_early_data", "SSL_set_recv_max_early_data: success"); |
| } else { |
| Debug("ssl_early_data", "SSL_set_recv_max_early_data: failed"); |
| } |
| |
| if (ret1 && ret2) { |
| Debug("ssl_early_data", "Must disable anti-replay if 0-rtt is enabled."); |
| SSL_set_options(ssl, SSL_OP_NO_ANTI_REPLAY); |
| } |
| } |
| #endif |
| } |
| this->_bindSSLObject(); |
| } |
| } |
| |
| void |
| SSLNetVConnection::_bindSSLObject() |
| { |
| SSLNetVCAttach(this->ssl, this); |
| TLSBasicSupport::bind(this->ssl, this); |
| ALPNSupport::bind(this->ssl, this); |
| TLSSessionResumptionSupport::bind(this->ssl, this); |
| TLSSNISupport::bind(this->ssl, this); |
| } |
| |
| void |
| SSLNetVConnection::_unbindSSLObject() |
| { |
| SSLNetVCDetach(this->ssl); |
| TLSBasicSupport::unbind(this->ssl); |
| ALPNSupport::unbind(this->ssl); |
| TLSSessionResumptionSupport::unbind(this->ssl); |
| TLSSNISupport::unbind(this->ssl); |
| } |
| |
| static void |
| debug_certificate_name(const char *msg, X509_NAME *name) |
| { |
| BIO *bio; |
| |
| if (name == nullptr) { |
| return; |
| } |
| |
| bio = BIO_new(BIO_s_mem()); |
| if (bio == nullptr) { |
| return; |
| } |
| |
| if (X509_NAME_print_ex(bio, name, 0 /* indent */, XN_FLAG_ONELINE) > 0) { |
| long len; |
| char *ptr; |
| len = BIO_get_mem_data(bio, &ptr); |
| Debug("ssl", "%s %.*s", msg, (int)len, ptr); |
| } |
| |
| BIO_free(bio); |
| } |
| |
| int |
| SSLNetVConnection::_ssl_read_from_net(EThread *lthread, int64_t &ret) |
| { |
| NetState *s = &this->read; |
| MIOBufferAccessor &buf = s->vio.buffer; |
| int event = SSL_READ_ERROR_NONE; |
| int64_t bytes_read = 0; |
| ssl_error_t sslErr = SSL_ERROR_NONE; |
| |
| int64_t toread = buf.writer()->write_avail(); |
| ink_release_assert(toread > 0); |
| if (toread > s->vio.ntodo()) { |
| toread = s->vio.ntodo(); |
| } |
| |
| bytes_read = 0; |
| while (sslErr == SSL_ERROR_NONE && bytes_read < toread) { |
| int64_t nread = 0; |
| int64_t block_write_avail = buf.writer()->block_write_avail(); |
| ink_release_assert(block_write_avail > 0); |
| int64_t amount_to_read = toread - bytes_read; |
| if (amount_to_read > block_write_avail) { |
| amount_to_read = block_write_avail; |
| } |
| |
| Debug("ssl", "amount_to_read=%" PRId64, amount_to_read); |
| char *current_block = buf.writer()->end(); |
| ink_release_assert(current_block != nullptr); |
| sslErr = this->_ssl_read_buffer(current_block, amount_to_read, nread); |
| |
| Debug("ssl", "nread=%" PRId64, nread); |
| |
| switch (sslErr) { |
| case SSL_ERROR_NONE: |
| |
| #if DEBUG |
| SSLDebugBufferPrint("ssl_buff", current_block, nread, "SSL Read"); |
| #endif |
| ink_assert(nread); |
| bytes_read += nread; |
| if (nread > 0) { |
| buf.writer()->fill(nread); // Tell the buffer, we've used the bytes |
| this->netActivity(lthread); |
| } |
| break; |
| case SSL_ERROR_WANT_WRITE: |
| event = SSL_WRITE_WOULD_BLOCK; |
| Debug("ssl.error", "SSL_ERROR_WOULD_BLOCK(write)"); |
| break; |
| case SSL_ERROR_WANT_READ: |
| event = SSL_READ_WOULD_BLOCK; |
| Debug("ssl.error", "SSL_ERROR_WOULD_BLOCK(read)"); |
| break; |
| #ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB |
| case SSL_ERROR_WANT_CLIENT_HELLO_CB: |
| event = SSL_READ_WOULD_BLOCK; |
| Debug("ssl.error", "SSL_ERROR_WOULD_BLOCK(read/client hello cb)"); |
| break; |
| #endif |
| case SSL_ERROR_WANT_X509_LOOKUP: |
| event = SSL_READ_WOULD_BLOCK; |
| Debug("ssl.error", "SSL_ERROR_WOULD_BLOCK(read/x509 lookup)"); |
| break; |
| case SSL_ERROR_SYSCALL: |
| if (nread != 0) { |
| // not EOF |
| SSL_INCREMENT_DYN_STAT(ssl_error_syscall); |
| event = SSL_READ_ERROR; |
| ret = errno; |
| Debug("ssl.error", "SSL_ERROR_SYSCALL, underlying IO error: %s", strerror(errno)); |
| } else { |
| // then EOF observed, treat it as EOS |
| event = SSL_READ_EOS; |
| } |
| break; |
| case SSL_ERROR_ZERO_RETURN: |
| event = SSL_READ_EOS; |
| Debug("ssl.error", "SSL_ERROR_ZERO_RETURN"); |
| break; |
| case SSL_ERROR_SSL: |
| default: { |
| char buf[512]; |
| unsigned long e = ERR_peek_last_error(); |
| ERR_error_string_n(e, buf, sizeof(buf)); |
| event = SSL_READ_ERROR; |
| ret = errno; |
| SSL_CLR_ERR_INCR_DYN_STAT(this, ssl_error_ssl, "errno=%d", errno); |
| } break; |
| } // switch |
| } // while |
| |
| if (bytes_read > 0) { |
| Debug("ssl", "bytes_read=%" PRId64, bytes_read); |
| |
| s->vio.ndone += bytes_read; |
| this->netActivity(lthread); |
| |
| ret = bytes_read; |
| |
| // If we read it all, don't worry about the other events and just send read complete |
| event = (s->vio.ntodo() <= 0) ? SSL_READ_COMPLETE : SSL_READ_READY; |
| if (sslErr == SSL_ERROR_NONE && s->vio.ntodo() > 0) { |
| // We stopped with data on the wire (to avoid overbuffering). Make sure we are triggered |
| this->read.triggered = 1; |
| } |
| } else { // if( bytes_read > 0 ) |
| #if defined(_DEBUG) |
| if (bytes_read == 0) { |
| Debug("ssl", "bytes_read == 0"); |
| } |
| #endif |
| } |
| return event; |
| } |
| |
| /** Read from socket directly for handshake data. Store the data in an MIOBuffer. Place the data in |
| * the read BIO so the openssl library has access to it. If for some reason we must abort out of the |
| * handshake, the stored data can be replayed (e.g. back out to blind tunneling) |
| */ |
| int64_t |
| SSLNetVConnection::read_raw_data() |
| { |
| // read data |
| int64_t r = 0; |
| int64_t total_read = 0; |
| int64_t rattempted = 0; |
| char *buffer = nullptr; |
| int buf_len; |
| IOBufferBlock *b = this->handShakeBuffer->first_write_block(); |
| |
| rattempted = b->write_avail(); |
| while (rattempted) { |
| buffer = b->_end; |
| buf_len = rattempted; |
| b = b->next.get(); |
| |
| r = socketManager.read(this->con.fd, buffer, buf_len); |
| NET_INCREMENT_DYN_STAT(net_calls_to_read_stat); |
| total_read += rattempted; |
| |
| Debug("ssl", "read_raw_data r=%" PRId64 " rattempted=%" PRId64 " total_read=%" PRId64 " fd=%d", r, rattempted, total_read, |
| con.fd); |
| // last read failed or was incomplete |
| if (r != rattempted || !b) { |
| break; |
| } |
| |
| rattempted = b->write_avail(); |
| } |
| // If we have already moved some bytes successfully, adjust total_read to reflect reality |
| // If any read succeeded, we should return success |
| if (r != rattempted) { |
| // If the first read fails, we should return error |
| if (r <= 0 && total_read > rattempted) { |
| r = total_read - rattempted; |
| } else { |
| r = total_read - rattempted + r; |
| } |
| } |
| NET_SUM_DYN_STAT(net_read_bytes_stat, r); |
| |
| IpMap *pp_ipmap; |
| pp_ipmap = SSLConfigParams::proxy_protocol_ipmap; |
| |
| if (this->get_is_proxy_protocol() && this->get_proxy_protocol_version() == ProxyProtocolVersion::UNDEFINED) { |
| Debug("proxyprotocol", "proxy protocol is enabled on this port"); |
| if (pp_ipmap->count() > 0) { |
| Debug("proxyprotocol", "proxy protocol has a configured allowlist of trusted IPs - checking"); |
| |
| // At this point, using get_remote_addr() will return the ip of the |
| // proxy source IP, not the Proxy Protocol client ip. Since we are |
| // checking the ip of the actual source of this connection, this is |
| // what we want now. |
| void *payload = nullptr; |
| if (!pp_ipmap->contains(get_remote_addr(), &payload)) { |
| Debug("proxyprotocol", "proxy protocol src IP is NOT in the configured allowlist of trusted IPs - " |
| "closing connection"); |
| r = -ENOTCONN; // Need a quick close/exit here to refuse the connection!!!!!!!!! |
| goto proxy_protocol_bypass; |
| } else { |
| char new_host[INET6_ADDRSTRLEN]; |
| Debug("proxyprotocol", "Source IP [%s] is in the trusted allowlist for proxy protocol", |
| ats_ip_ntop(this->get_remote_addr(), new_host, sizeof(new_host))); |
| } |
| } else { |
| Debug("proxyprotocol", "proxy protocol DOES NOT have a configured allowlist of trusted IPs but " |
| "proxy protocol is enabled on this port - processing all connections"); |
| } |
| |
| if (this->has_proxy_protocol(buffer, &r)) { |
| Debug("proxyprotocol", "ssl has proxy protocol header"); |
| set_remote_addr(get_proxy_protocol_src_addr()); |
| if (is_debug_tag_set("proxyprotocol")) { |
| IpEndpoint dst; |
| dst.sa = *(this->get_proxy_protocol_dst_addr()); |
| ip_port_text_buffer ipb1; |
| ats_ip_nptop(&dst, ipb1, sizeof(ipb1)); |
| Debug("proxyprotocol", "ssl_has_proxy_v1, dest IP received [%s]", ipb1); |
| } |
| } else { |
| Debug("proxyprotocol", "proxy protocol was enabled, but required header was not present in the " |
| "transaction - closing connection"); |
| } |
| } // end of Proxy Protocol processing |
| |
| proxy_protocol_bypass: |
| |
| if (r > 0) { |
| this->handShakeBuffer->fill(r); |
| |
| char *start = this->handShakeReader->start(); |
| char *end = this->handShakeReader->end(); |
| this->handShakeBioStored = end - start; |
| |
| // Sets up the buffer as a read only bio target |
| // Must be reset on each read |
| BIO *rbio = BIO_new_mem_buf(start, this->handShakeBioStored); |
| BIO_set_mem_eof_return(rbio, -1); |
| SSL_set0_rbio(this->ssl, rbio); |
| } else { |
| this->handShakeBioStored = 0; |
| } |
| |
| Debug("ssl", "%p read r=%" PRId64 " total=%" PRId64 " bio=%d\n", this, r, total_read, this->handShakeBioStored); |
| |
| // check for errors |
| if (r <= 0) { |
| if (r == -EAGAIN || r == -ENOTCONN) { |
| NET_INCREMENT_DYN_STAT(net_calls_to_read_nodata_stat); |
| } |
| } |
| |
| return r; |
| } |
| |
| // |
| // Return true if we updated the rbio with another |
| // memory chunk (should be ready for another read right away) |
| // |
| bool |
| SSLNetVConnection::update_rbio(bool move_to_socket) |
| { |
| bool retval = false; |
| if (BIO_eof(SSL_get_rbio(this->ssl)) && this->handShakeReader != nullptr) { |
| this->handShakeReader->consume(this->handShakeBioStored); |
| this->handShakeBioStored = 0; |
| // Load up the next block if present |
| if (this->handShakeReader->is_read_avail_more_than(0)) { |
| // Setup the next iobuffer block to drain |
| char *start = this->handShakeReader->start(); |
| char *end = this->handShakeReader->end(); |
| this->handShakeBioStored = end - start; |
| |
| // Sets up the buffer as a read only bio target |
| // Must be reset on each read |
| BIO *rbio = BIO_new_mem_buf(start, this->handShakeBioStored); |
| BIO_set_mem_eof_return(rbio, -1); |
| SSL_set0_rbio(this->ssl, rbio); |
| retval = true; |
| // Handshake buffer is empty but we have read something, move to the socket rbio |
| } else if (move_to_socket && this->handShakeHolder->is_read_avail_more_than(0)) { |
| BIO *rbio = BIO_new_fd(this->get_socket(), BIO_NOCLOSE); |
| BIO_set_mem_eof_return(rbio, -1); |
| SSL_set0_rbio(this->ssl, rbio); |
| free_handshake_buffers(); |
| } |
| } |
| return retval; |
| } |
| |
| // changed by YTS Team, yamsat |
| void |
| SSLNetVConnection::net_read_io(NetHandler *nh, EThread *lthread) |
| { |
| int ret; |
| int64_t r = 0; |
| int64_t bytes = 0; |
| NetState *s = &this->read; |
| |
| if (HttpProxyPort::TRANSPORT_BLIND_TUNNEL == this->attributes) { |
| this->super::net_read_io(nh, lthread); |
| return; |
| } |
| |
| MUTEX_TRY_LOCK(lock, s->vio.mutex, lthread); |
| if (!lock.is_locked()) { |
| readReschedule(nh); |
| return; |
| } |
| // Got closed by the HttpSessionManager thread during a migration |
| // The closed flag should be stable once we get the s->vio.mutex in that case |
| // (the global session pool mutex). |
| if (this->closed) { |
| this->super::net_read_io(nh, lthread); |
| return; |
| } |
| // If the key renegotiation failed it's over, just signal the error and finish. |
| if (sslClientRenegotiationAbort == true) { |
| this->read.triggered = 0; |
| readSignalError(nh, -ENET_SSL_FAILED); |
| Debug("ssl", "client renegotiation setting read signal error"); |
| return; |
| } |
| |
| // If it is not enabled, lower its priority. This allows |
| // a fast connection to speed match a slower connection by |
| // shifting down in priority even if it could read. |
| if (!s->enabled || s->vio.op != VIO::READ || s->vio.is_disabled()) { |
| read_disable(nh, this); |
| return; |
| } |
| |
| MIOBufferAccessor &buf = s->vio.buffer; |
| int64_t ntodo = s->vio.ntodo(); |
| ink_assert(buf.writer()); |
| |
| // Continue on if we are still in the handshake |
| if (!getSSLHandShakeComplete()) { |
| int err = 0; |
| |
| if (get_context() == NET_VCONNECTION_OUT) { |
| ret = sslStartHandShake(SSL_EVENT_CLIENT, err); |
| } else { |
| ret = sslStartHandShake(SSL_EVENT_SERVER, err); |
| } |
| // If we have flipped to blind tunnel, don't read ahead |
| if (this->handShakeReader) { |
| if (this->attributes == HttpProxyPort::TRANSPORT_BLIND_TUNNEL) { |
| // Now in blind tunnel. Set things up to read what is in the buffer |
| // Must send the READ_COMPLETE here before considering |
| // forwarding on the handshake buffer, so the |
| // SSLNextProtocolTrampoline has a chance to do its |
| // thing before forwarding the buffers. |
| this->readSignalDone(VC_EVENT_READ_COMPLETE, nh); |
| |
| // If the handshake isn't set yet, this means the tunnel |
| // decision was make in the SNI callback. We must move |
| // the client hello message back into the standard read.vio |
| // so it will get forwarded onto the origin server |
| if (!this->getSSLHandShakeComplete()) { |
| this->sslHandshakeStatus = SSL_HANDSHAKE_DONE; |
| |
| // Copy over all data already read in during the SSL_accept |
| // (the client hello message) |
| NetState *s = &this->read; |
| MIOBufferAccessor &buf = s->vio.buffer; |
| int64_t r = buf.writer()->write(this->handShakeHolder); |
| s->vio.nbytes += r; |
| s->vio.ndone += r; |
| |
| // Clean up the handshake buffers |
| this->free_handshake_buffers(); |
| |
| if (r > 0) { |
| // Kick things again, so the data that was copied into the |
| // vio.read buffer gets processed |
| this->readSignalDone(VC_EVENT_READ_COMPLETE, nh); |
| } |
| } |
| return; // Leave if we are tunneling |
| } |
| } |
| if (ret == EVENT_ERROR) { |
| this->read.triggered = 0; |
| readSignalError(nh, err); |
| } else if (ret == SSL_HANDSHAKE_WANT_READ || ret == SSL_HANDSHAKE_WANT_ACCEPT) { |
| if (SSLConfigParams::ssl_handshake_timeout_in > 0) { |
| double handshake_time = (static_cast<double>(Thread::get_hrtime() - this->get_tls_handshake_begin_time()) / 1000000000); |
| Debug("ssl", "ssl handshake for vc %p, took %.3f seconds, configured handshake_timer: %d", this, handshake_time, |
| SSLConfigParams::ssl_handshake_timeout_in); |
| if (handshake_time > SSLConfigParams::ssl_handshake_timeout_in) { |
| Debug("ssl", "ssl handshake for vc %p, expired, release the connection", this); |
| read.triggered = 0; |
| nh->read_ready_list.remove(this); |
| readSignalError(nh, ETIMEDOUT); |
| return; |
| } |
| } |
| // move over to the socket if we haven't already |
| if (this->handShakeBuffer != nullptr) { |
| read.triggered = update_rbio(true); |
| } else { |
| read.triggered = 0; |
| } |
| if (!read.triggered) { |
| nh->read_ready_list.remove(this); |
| } |
| readReschedule(nh); |
| } else if (ret == SSL_HANDSHAKE_WANT_CONNECT || ret == SSL_HANDSHAKE_WANT_WRITE) { |
| write.triggered = 0; |
| nh->write_ready_list.remove(this); |
| writeReschedule(nh); |
| } else if (ret == EVENT_DONE) { |
| Debug("ssl", "ssl handshake EVENT_DONE ntodo=%" PRId64, ntodo); |
| // If this was driven by a zero length read, signal complete when |
| // the handshake is complete. Otherwise set up for continuing read |
| // operations. |
| if (ntodo <= 0) { |
| readSignalDone(VC_EVENT_READ_COMPLETE, nh); |
| } else { |
| read.triggered = 1; |
| if (read.enabled) { |
| nh->read_ready_list.in_or_enqueue(this); |
| } |
| } |
| } else if (ret == SSL_WAIT_FOR_HOOK || ret == SSL_WAIT_FOR_ASYNC) { |
| // avoid readReschedule - done when the plugin calls us back to reenable |
| } else { |
| readReschedule(nh); |
| } |
| return; |
| } |
| |
| // If there is nothing to do or no space available, disable connection |
| if (ntodo <= 0 || !buf.writer()->write_avail() || s->vio.is_disabled()) { |
| read_disable(nh, this); |
| return; |
| } |
| |
| // At this point we are at the post-handshake SSL processing |
| // |
| // not sure if this do-while loop is really needed here, please replace |
| // this comment if you know |
| int ssl_read_errno = 0; |
| do { |
| ret = this->_ssl_read_from_net(lthread, r); |
| if (ret == SSL_READ_READY || ret == SSL_READ_ERROR_NONE) { |
| bytes += r; |
| } |
| ink_assert(bytes >= 0); |
| } while ((ret == SSL_READ_READY && bytes == 0) || ret == SSL_READ_ERROR_NONE); |
| ssl_read_errno = errno; |
| |
| if (bytes > 0) { |
| if (ret == SSL_READ_WOULD_BLOCK || ret == SSL_READ_READY) { |
| if (readSignalAndUpdate(VC_EVENT_READ_READY) != EVENT_CONT) { |
| Debug("ssl", "readSignal != EVENT_CONT"); |
| return; |
| } |
| } |
| } |
| |
| switch (ret) { |
| case SSL_READ_READY: |
| readReschedule(nh); |
| return; |
| break; |
| case SSL_WRITE_WOULD_BLOCK: |
| case SSL_READ_WOULD_BLOCK: |
| if (lock.get_mutex() != s->vio.mutex.get()) { |
| Debug("ssl", "mutex switched"); |
| if (ret == SSL_READ_WOULD_BLOCK) { |
| readReschedule(nh); |
| } else { |
| writeReschedule(nh); |
| } |
| return; |
| } |
| // reset the trigger and remove from the ready queue |
| // we will need to be retriggered to read from this socket again |
| read.triggered = 0; |
| nh->read_ready_list.remove(this); |
| Debug("ssl", "read finished - would block"); |
| #if TS_USE_PORT |
| if (ret == SSL_READ_WOULD_BLOCK) { |
| readReschedule(nh); |
| } else { |
| writeReschedule(nh); |
| } |
| #endif |
| break; |
| |
| case SSL_READ_EOS: |
| // close the connection if we have SSL_READ_EOS, this is the return value from ssl_read_from_net() if we get an |
| // SSL_ERROR_ZERO_RETURN from SSL_get_error() |
| // SSL_ERROR_ZERO_RETURN means that the origin server closed the SSL connection |
| read.triggered = 0; |
| readSignalDone(VC_EVENT_EOS, nh); |
| |
| if (bytes > 0) { |
| Debug("ssl", "read finished - EOS"); |
| } else { |
| Debug("ssl", "read finished - 0 useful bytes read, bytes used by SSL layer"); |
| } |
| break; |
| case SSL_READ_COMPLETE: |
| readSignalDone(VC_EVENT_READ_COMPLETE, nh); |
| Debug("ssl", "read finished - signal done"); |
| break; |
| case SSL_READ_ERROR: |
| this->read.triggered = 0; |
| readSignalError(nh, (ssl_read_errno) ? ssl_read_errno : -ENET_SSL_FAILED); |
| Debug("ssl", "read finished - read error"); |
| break; |
| } |
| } |
| |
| int64_t |
| SSLNetVConnection::load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs) |
| { |
| int64_t try_to_write; |
| int64_t num_really_written = 0; |
| int64_t l = 0; |
| uint32_t dynamic_tls_record_size = 0; |
| ssl_error_t err = SSL_ERROR_NONE; |
| |
| // Dynamic TLS record sizing |
| ink_hrtime now = 0; |
| if (SSLConfigParams::ssl_maxrecord == -1) { |
| now = Thread::get_hrtime_updated(); |
| int msec_since_last_write = ink_hrtime_diff_msec(now, sslLastWriteTime); |
| |
| if (msec_since_last_write > SSL_DEF_TLS_RECORD_MSEC_THRESHOLD) { |
| // reset sslTotalBytesSent upon inactivity for SSL_DEF_TLS_RECORD_MSEC_THRESHOLD |
| sslTotalBytesSent = 0; |
| } |
| Debug("ssl", "now=%" PRId64 " lastwrite=%" PRId64 " msec_since_last_write=%d", now, sslLastWriteTime, msec_since_last_write); |
| } |
| |
| if (HttpProxyPort::TRANSPORT_BLIND_TUNNEL == this->attributes) { |
| return this->super::load_buffer_and_write(towrite, buf, total_written, needs); |
| } |
| |
| Debug("ssl", "towrite=%" PRId64, towrite); |
| |
| do { |
| // What is remaining left in the next block? |
| l = buf.reader()->block_read_avail(); |
| char *current_block = buf.reader()->start(); |
| |
| // check if to amount to write exceeds that in this buffer |
| int64_t wavail = towrite - total_written; |
| |
| if (l > wavail) { |
| l = wavail; |
| } |
| |
| // TS-2365: If the SSL max record size is set and we have |
| // more data than that, break this into smaller write |
| // operations. |
| // |
| // TS-4424: Don't mess with record size if last SSL_write failed with |
| // needs write |
| if (redoWriteSize) { |
| l = redoWriteSize; |
| redoWriteSize = 0; |
| } else { |
| if (SSLConfigParams::ssl_maxrecord > 0 && l > SSLConfigParams::ssl_maxrecord) { |
| l = SSLConfigParams::ssl_maxrecord; |
| } else if (SSLConfigParams::ssl_maxrecord == -1) { |
| if (sslTotalBytesSent < SSL_DEF_TLS_RECORD_BYTE_THRESHOLD) { |
| dynamic_tls_record_size = SSL_DEF_TLS_RECORD_SIZE; |
| SSL_INCREMENT_DYN_STAT(ssl_total_dyn_def_tls_record_count); |
| } else { |
| dynamic_tls_record_size = SSL_MAX_TLS_RECORD_SIZE; |
| SSL_INCREMENT_DYN_STAT(ssl_total_dyn_max_tls_record_count); |
| } |
| if (l > dynamic_tls_record_size) { |
| l = dynamic_tls_record_size; |
| } |
| } |
| } |
| |
| if (!l) { |
| break; |
| } |
| |
| try_to_write = l; |
| num_really_written = 0; |
| Debug("v_ssl", "b=%p l=%" PRId64, current_block, l); |
| err = this->_ssl_write_buffer(current_block, l, num_really_written); |
| |
| // We wrote all that we thought we should |
| if (num_really_written > 0) { |
| total_written += num_really_written; |
| buf.reader()->consume(num_really_written); |
| } |
| |
| Debug("ssl", "try_to_write=%" PRId64 " written=%" PRId64 " total_written=%" PRId64, try_to_write, num_really_written, |
| total_written); |
| NET_INCREMENT_DYN_STAT(net_calls_to_write_stat); |
| } while (num_really_written == try_to_write && total_written < towrite); |
| |
| if (total_written > 0) { |
| sslLastWriteTime = now; |
| sslTotalBytesSent += total_written; |
| } |
| redoWriteSize = 0; |
| if (num_really_written > 0) { |
| needs |= EVENTIO_WRITE; |
| } else { |
| switch (err) { |
| case SSL_ERROR_NONE: |
| Debug("ssl", "SSL_write-SSL_ERROR_NONE"); |
| break; |
| case SSL_ERROR_WANT_READ: |
| needs |= EVENTIO_READ; |
| num_really_written = -EAGAIN; |
| Debug("ssl.error", "SSL_write-SSL_ERROR_WANT_READ"); |
| break; |
| case SSL_ERROR_WANT_WRITE: |
| #ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB |
| case SSL_ERROR_WANT_CLIENT_HELLO_CB: |
| #endif |
| case SSL_ERROR_WANT_X509_LOOKUP: { |
| if (SSL_ERROR_WANT_WRITE == err) { |
| redoWriteSize = l; |
| } |
| needs |= EVENTIO_WRITE; |
| num_really_written = -EAGAIN; |
| Debug("ssl.error", "SSL_write-SSL_ERROR_WANT_WRITE"); |
| break; |
| } |
| case SSL_ERROR_SYSCALL: |
| num_really_written = -errno; |
| SSL_INCREMENT_DYN_STAT(ssl_error_syscall); |
| Debug("ssl.error", "SSL_write-SSL_ERROR_SYSCALL"); |
| break; |
| // end of stream |
| case SSL_ERROR_ZERO_RETURN: |
| num_really_written = -errno; |
| Debug("ssl.error", "SSL_write-SSL_ERROR_ZERO_RETURN"); |
| break; |
| case SSL_ERROR_SSL: |
| default: { |
| // Treat SSL_ERROR_SSL as EPIPE error. |
| num_really_written = -EPIPE; |
| SSL_CLR_ERR_INCR_DYN_STAT(this, ssl_error_ssl, "SSL_write-SSL_ERROR_SSL errno=%d", errno); |
| } break; |
| } |
| } |
| return num_really_written; |
| } |
| |
| SSLNetVConnection::SSLNetVConnection() {} |
| |
| void |
| SSLNetVConnection::do_io_close(int lerrno) |
| { |
| if (this->ssl != nullptr) { |
| if (get_context() == NET_VCONNECTION_OUT) { |
| callHooks(TS_EVENT_VCONN_OUTBOUND_CLOSE); |
| } else { |
| callHooks(TS_EVENT_VCONN_CLOSE); |
| } |
| |
| if (getSSLHandShakeComplete()) { |
| int shutdown_mode = SSL_get_shutdown(ssl); |
| Debug("ssl-shutdown", "previous shutdown state 0x%x", shutdown_mode); |
| int new_shutdown_mode = shutdown_mode | SSL_RECEIVED_SHUTDOWN; |
| |
| if (new_shutdown_mode != shutdown_mode) { |
| // We do not need to sit around and wait for the client's close-notify if |
| // they have not already sent it. We will still be standards compliant |
| Debug("ssl-shutdown", "new SSL_set_shutdown 0x%x", new_shutdown_mode); |
| SSL_set_shutdown(ssl, new_shutdown_mode); |
| } |
| |
| // If the peer has already sent a FIN, don't bother with the shutdown |
| // They will just send us a RST for our troubles |
| // This test is not foolproof. The client's fin could be on the wire |
| // at the same time we send the close-notify. If so, the client will likely |
| // send RST anyway |
| char c; |
| ssize_t x = recv(this->con.fd, &c, 1, MSG_PEEK); |
| // x < 0 means error. x == 0 means fin sent |
| bool do_shutdown = (x > 0); |
| if (x < 0) { |
| do_shutdown = (errno == EAGAIN || errno == EWOULDBLOCK); |
| } |
| if (do_shutdown) { |
| // Send the close-notify |
| int ret = SSL_shutdown(ssl); |
| Debug("ssl-shutdown", "SSL_shutdown %s", (ret) ? "success" : "failed"); |
| } else { |
| // Request a quiet shutdown to OpenSSL |
| SSL_set_quiet_shutdown(ssl, 1); |
| SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN | SSL_SENT_SHUTDOWN); |
| Debug("ssl-shutdown", "Enable quiet shutdown"); |
| } |
| } |
| } |
| // Go on and do the unix socket cleanups |
| super::do_io_close(lerrno); |
| } |
| |
| void |
| SSLNetVConnection::clear() |
| { |
| _ca_cert_file.reset(); |
| _ca_cert_dir.reset(); |
| |
| if (ssl != nullptr) { |
| SSL_free(ssl); |
| ssl = nullptr; |
| } |
| ALPNSupport::clear(); |
| TLSBasicSupport::clear(); |
| TLSSessionResumptionSupport::clear(); |
| TLSSNISupport::_clear(); |
| |
| sslHandshakeStatus = SSL_HANDSHAKE_ONGOING; |
| sslLastWriteTime = 0; |
| sslTotalBytesSent = 0; |
| sslClientRenegotiationAbort = false; |
| |
| curHook = nullptr; |
| hookOpRequested = SSL_HOOK_OP_DEFAULT; |
| free_handshake_buffers(); |
| |
| super::clear(); |
| } |
| void |
| SSLNetVConnection::free(EThread *t) |
| { |
| ink_release_assert(t == this_ethread()); |
| |
| // close socket fd |
| if (con.fd != NO_FD) { |
| NET_SUM_GLOBAL_DYN_STAT(net_connections_currently_open_stat, -1); |
| } |
| con.close(); |
| |
| ats_free(tunnel_host); |
| |
| if (early_data_reader != nullptr) { |
| early_data_reader->dealloc(); |
| } |
| |
| if (early_data_buf != nullptr) { |
| free_MIOBuffer(early_data_buf); |
| } |
| |
| early_data_reader = nullptr; |
| early_data_buf = nullptr; |
| |
| clear(); |
| ink_assert(con.fd == NO_FD); |
| ink_assert(t == this_ethread()); |
| |
| if (from_accept_thread) { |
| sslNetVCAllocator.free(this); |
| } else { |
| ink_assert(con.fd == NO_FD); |
| THREAD_FREE(this, sslNetVCAllocator, t); |
| } |
| } |
| |
| int |
| SSLNetVConnection::sslStartHandShake(int event, int &err) |
| { |
| if (TSSystemState::is_ssl_handshaking_stopped()) { |
| Debug("ssl", "Stopping handshake due to server shutting down."); |
| return EVENT_ERROR; |
| } |
| if (this->get_tls_handshake_begin_time() == 0) { |
| this->_record_tls_handshake_begin_time(); |
| // net_activity will not be triggered until after the handshake |
| set_inactivity_timeout(HRTIME_SECONDS(SSLConfigParams::ssl_handshake_timeout_in)); |
| } |
| SSLConfig::scoped_config params; |
| switch (event) { |
| case SSL_EVENT_SERVER: |
| if (this->ssl == nullptr) { |
| SSLCertificateConfig::scoped_config lookup; |
| IpEndpoint dst; |
| int namelen = sizeof(dst); |
| if (0 != safe_getsockname(this->get_socket(), &dst.sa, &namelen)) { |
| Debug("ssl", "Failed to get dest ip, errno = [%d]", errno); |
| return EVENT_ERROR; |
| } |
| SSLCertContext *cc = lookup->find(dst); |
| if (is_debug_tag_set("ssl")) { |
| IpEndpoint src; |
| ip_port_text_buffer ipb1, ipb2; |
| int ip_len = sizeof(src); |
| |
| if (0 != safe_getpeername(this->get_socket(), &src.sa, &ip_len)) { |
| Debug("ssl", "Failed to get src ip, errno = [%d]", errno); |
| return EVENT_ERROR; |
| } |
| ats_ip_nptop(&dst, ipb1, sizeof(ipb1)); |
| ats_ip_nptop(&src, ipb2, sizeof(ipb2)); |
| Debug("ssl", "IP context is %p for [%s] -> [%s], default context %p", cc, ipb2, ipb1, lookup->defaultContext()); |
| } |
| |
| // Escape if this is marked to be a tunnel. |
| // No data has been read at this point, so we can go |
| // directly into blind tunnel mode |
| |
| if (cc && SSLCertContextOption::OPT_TUNNEL == cc->opt) { |
| if (this->is_transparent) { |
| this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; |
| sslHandshakeStatus = SSL_HANDSHAKE_DONE; |
| SSL_free(this->ssl); |
| this->ssl = nullptr; |
| return EVENT_DONE; |
| } else { |
| hookOpRequested = SSL_HOOK_OP_TUNNEL; |
| } |
| } |
| |
| // Attach the default SSL_CTX to this SSL session. The default context is never going to be able |
| // to negotiate a SSL session, but it's enough to trampoline us into the SNI callback where we |
| // can select the right server certificate. |
| this->_make_ssl_connection(lookup->defaultContext()); |
| } |
| |
| if (this->ssl == nullptr) { |
| SSLErrorVC(this, "failed to create SSL server session"); |
| return EVENT_ERROR; |
| } |
| return sslServerHandShakeEvent(err); |
| |
| case SSL_EVENT_CLIENT: |
| |
| char buff[INET6_ADDRSTRLEN]; |
| |
| if (this->ssl == nullptr) { |
| // Making the check here instead of later, so we only |
| // do this setting immediately after we create the SSL object |
| SNIConfig::scoped_config sniParam; |
| const char *serverKey = this->options.sni_servername; |
| if (!serverKey) { |
| ats_ip_ntop(this->get_remote_addr(), buff, INET6_ADDRSTRLEN); |
| serverKey = buff; |
| } |
| auto nps = sniParam->getPropertyConfig(serverKey); |
| shared_SSL_CTX sharedCTX = nullptr; |
| SSL_CTX *clientCTX = nullptr; |
| |
| // First Look to see if there are override parameters |
| if (options.ssl_client_cert_name) { |
| std::string certFilePath = Layout::get()->relative_to(params->clientCertPathOnly, options.ssl_client_cert_name.get()); |
| std::string keyFilePath; |
| if (options.ssl_client_private_key_name) { |
| keyFilePath = Layout::get()->relative_to(params->clientKeyPathOnly, options.ssl_client_private_key_name); |
| } |
| std::string caCertFilePath; |
| if (options.ssl_client_ca_cert_name) { |
| caCertFilePath = Layout::get()->relative_to(params->clientCACertPath, options.ssl_client_ca_cert_name); |
| } |
| sharedCTX = |
| params->getCTX(certFilePath, keyFilePath, caCertFilePath.empty() ? params->clientCACertFilename : caCertFilePath.c_str(), |
| params->clientCACertPath); |
| } else if (options.ssl_client_ca_cert_name) { |
| std::string caCertFilePath = Layout::get()->relative_to(params->clientCACertPath, options.ssl_client_ca_cert_name); |
| sharedCTX = params->getCTX(params->clientCertPath, params->clientKeyPath, caCertFilePath.c_str(), params->clientCACertPath); |
| } else if (nps && !nps->client_cert_file.empty()) { |
| // If no overrides available, try the available nextHopProperty by reading from context mappings |
| sharedCTX = |
| params->getCTX(nps->client_cert_file, nps->client_key_file, params->clientCACertFilename, params->clientCACertPath); |
| } else { // Just stay with the values passed down from the SM for verify |
| clientCTX = params->client_ctx.get(); |
| } |
| |
| if (sharedCTX) { |
| clientCTX = sharedCTX.get(); |
| } |
| |
| if (options.verifyServerPolicy != YamlSNIConfig::Policy::UNSET) { |
| // Stay with conf-override version as the highest priority |
| } else if (nps && nps->verifyServerPolicy != YamlSNIConfig::Policy::UNSET) { |
| options.verifyServerPolicy = nps->verifyServerPolicy; |
| } else { |
| options.verifyServerPolicy = params->verifyServerPolicy; |
| } |
| |
| if (options.verifyServerProperties != YamlSNIConfig::Property::UNSET) { |
| // Stay with conf-override version as the highest priority |
| } else if (nps && nps->verifyServerProperties != YamlSNIConfig::Property::UNSET) { |
| options.verifyServerProperties = nps->verifyServerProperties; |
| } else { |
| options.verifyServerProperties = params->verifyServerProperties; |
| } |
| |
| if (!clientCTX) { |
| SSLErrorVC(this, "failed to create SSL client session"); |
| return EVENT_ERROR; |
| } |
| |
| this->_make_ssl_connection(clientCTX); |
| if (this->ssl == nullptr) { |
| SSLErrorVC(this, "failed to create SSL client session"); |
| return EVENT_ERROR; |
| } |
| |
| SSL_set_verify(this->ssl, SSL_VERIFY_PEER, verify_callback); |
| |
| // SNI |
| ats_scoped_str &tlsext_host_name = this->options.sni_hostname ? this->options.sni_hostname : this->options.sni_servername; |
| if (tlsext_host_name) { |
| if (SSL_set_tlsext_host_name(this->ssl, tlsext_host_name)) { |
| Debug("ssl", "using SNI name '%s' for client handshake", tlsext_host_name.get()); |
| } else { |
| Debug("ssl.error", "failed to set SNI name '%s' for client handshake", tlsext_host_name.get()); |
| SSL_INCREMENT_DYN_STAT(ssl_sni_name_set_failure); |
| } |
| } |
| |
| // ALPN |
| if (!this->options.alpn_protos.empty()) { |
| if (int res = SSL_set_alpn_protos(this->ssl, reinterpret_cast<const uint8_t *>(this->options.alpn_protos.data()), |
| this->options.alpn_protos.size()); |
| res != 0) { |
| Debug("ssl.error", "failed to set ALPN '%.*s' for client handshake", static_cast<int>(this->options.alpn_protos.size()), |
| this->options.alpn_protos.data()); |
| } |
| } |
| } |
| |
| return sslClientHandShakeEvent(err); |
| |
| default: |
| ink_assert(0); |
| return EVENT_ERROR; |
| } |
| } |
| |
| int |
| SSLNetVConnection::sslServerHandShakeEvent(int &err) |
| { |
| // Continue on if we are in the invoked state. The hook has not yet reenabled |
| if (sslHandshakeHookState == HANDSHAKE_HOOKS_CERT_INVOKE || sslHandshakeHookState == HANDSHAKE_HOOKS_CLIENT_CERT_INVOKE || |
| sslHandshakeHookState == HANDSHAKE_HOOKS_PRE_INVOKE || sslHandshakeHookState == HANDSHAKE_HOOKS_CLIENT_HELLO_INVOKE) { |
| return SSL_WAIT_FOR_HOOK; |
| } |
| |
| // Go do the preaccept hooks |
| if (sslHandshakeHookState == HANDSHAKE_HOOKS_PRE) { |
| SSL_INCREMENT_DYN_STAT(ssl_total_attempts_handshake_count_in_stat); |
| if (!curHook) { |
| Debug("ssl", "Initialize preaccept curHook from NULL"); |
| curHook = ssl_hooks->get(TSSslHookInternalID(TS_VCONN_START_HOOK)); |
| } else { |
| curHook = curHook->next(); |
| } |
| // If no more hooks, move onto CLIENT HELLO |
| |
| if (nullptr == curHook) { |
| sslHandshakeHookState = HANDSHAKE_HOOKS_CLIENT_HELLO; |
| } else { |
| sslHandshakeHookState = HANDSHAKE_HOOKS_PRE_INVOKE; |
| ContWrapper::wrap(nh->mutex.get(), curHook->m_cont, TS_EVENT_VCONN_START, this); |
| return SSL_WAIT_FOR_HOOK; |
| } |
| } |
| |
| // If a blind tunnel was requested in the pre-accept calls, convert. |
| // Again no data has been exchanged, so we can go directly |
| // without data replay. |
| // Note we can't arrive here if a hook is active. |
| |
| if (SSL_HOOK_OP_TUNNEL == hookOpRequested) { |
| this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; |
| SSL_free(this->ssl); |
| this->ssl = nullptr; |
| // Don't mark the handshake as complete yet, |
| // Will be checking for that flag not being set after |
| // we get out of this callback, and then will shuffle |
| // over the buffered handshake packets to the O.S. |
| return EVENT_DONE; |
| } else if (SSL_HOOK_OP_TERMINATE == hookOpRequested) { |
| sslHandshakeStatus = SSL_HANDSHAKE_DONE; |
| return EVENT_DONE; |
| } |
| |
| Debug("ssl", "Go on with the handshake state=%d", sslHandshakeHookState); |
| |
| // All the pre-accept hooks have completed, proceed with the actual accept. |
| if (this->handShakeReader) { |
| if (BIO_eof(SSL_get_rbio(this->ssl))) { // No more data in the buffer |
| // Is this the first read? |
| if (!this->handShakeReader->is_read_avail_more_than(0) && !this->handShakeHolder->is_read_avail_more_than(0)) { |
| #if TS_USE_TLS_ASYNC |
| if (SSLConfigParams::async_handshake_enabled) { |
| SSL_set_mode(ssl, SSL_MODE_ASYNC); |
| } |
| #endif |
| |
| Debug("ssl", "%p first read\n", this); |
| // Read from socket to fill in the BIO buffer with the |
| // raw handshake data before calling the ssl accept calls. |
| int retval = this->read_raw_data(); |
| if (retval < 0) { |
| if (retval == -EAGAIN) { |
| // No data at the moment, hang tight |
| SSLVCDebug(this, "SSL handshake: EAGAIN"); |
| return SSL_HANDSHAKE_WANT_READ; |
| } else { |
| // An error, make us go away |
| SSLVCDebug(this, "SSL handshake error: read_retval=%d", retval); |
| return EVENT_ERROR; |
| } |
| } else if (retval == 0) { |
| // EOF, go away, we stopped in the handshake |
| SSLVCDebug(this, "SSL handshake error: EOF"); |
| return EVENT_ERROR; |
| } |
| } else { |
| update_rbio(false); |
| } |
| } // Still data in the BIO |
| } |
| |
| ssl_error_t ssl_error = this->_ssl_accept(); |
| #if TS_USE_TLS_ASYNC |
| if (ssl_error == SSL_ERROR_WANT_ASYNC) { |
| // Do we need to set up the async eventfd? Or is it already registered? |
| if (async_ep.fd < 0) { |
| size_t numfds; |
| OSSL_ASYNC_FD *waitfds; |
| // Set up the epoll entry for the signalling |
| if (SSL_get_all_async_fds(ssl, nullptr, &numfds) && numfds > 0) { |
| // Allocate space for the waitfd on the stack, should only be one most all of the time |
| waitfds = reinterpret_cast<OSSL_ASYNC_FD *>(alloca(sizeof(OSSL_ASYNC_FD) * numfds)); |
| if (SSL_get_all_async_fds(ssl, waitfds, &numfds) && numfds > 0) { |
| this->read.triggered = false; |
| this->write.triggered = false; |
| // Have to have the read NetState enabled because we are using it for the signal vc |
| read.enabled = true; |
| PollDescriptor *pd = get_PollDescriptor(this_ethread()); |
| this->async_ep.start(pd, waitfds[0], static_cast<NetEvent *>(this), EVENTIO_READ); |
| this->async_ep.type = EVENTIO_READWRITE_VC; |
| } |
| } |
| } |
| } else if (SSLConfigParams::async_handshake_enabled) { |
| // Make sure the net fd read vio is in the right state |
| if (ssl_error == SSL_ERROR_WANT_READ) { |
| this->reenable(&read.vio); |
| this->read.triggered = 1; |
| } |
| } |
| #endif |
| if (ssl_error != SSL_ERROR_NONE) { |
| err = errno; |
| SSLVCDebug(this, "SSL handshake error: %s (%d), errno=%d", SSLErrorName(ssl_error), ssl_error, err); |
| |
| // start a blind tunnel if tr-pass is set and data does not look like ClientHello |
| char *buf = handShakeBuffer ? handShakeBuffer->buf() : nullptr; |
| if (getTransparentPassThrough() && buf && *buf != SSL_OP_HANDSHAKE) { |
| SSLVCDebug(this, "Data does not look like SSL handshake, starting blind tunnel"); |
| this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; |
| sslHandshakeStatus = SSL_HANDSHAKE_ONGOING; |
| return EVENT_CONT; |
| } |
| } |
| |
| switch (ssl_error) { |
| case SSL_ERROR_NONE: |
| if (is_debug_tag_set("ssl")) { |
| X509 *cert = SSL_get_peer_certificate(ssl); |
| |
| Debug("ssl", "SSL server handshake completed successfully"); |
| if (cert) { |
| debug_certificate_name("client certificate subject CN is", X509_get_subject_name(cert)); |
| debug_certificate_name("client certificate issuer CN is", X509_get_issuer_name(cert)); |
| X509_free(cert); |
| } |
| } |
| |
| sslHandshakeStatus = SSL_HANDSHAKE_DONE; |
| |
| if (this->get_tls_handshake_begin_time()) { |
| this->_record_tls_handshake_end_time(); |
| SSL_INCREMENT_DYN_STAT(ssl_total_success_handshake_count_in_stat); |
| } |
| |
| if (_tunnel_type != SNIRoutingType::NONE) { |
| // Foce to use HTTP/1.1 endpoint for SNI Routing |
| if (!this->setSelectedProtocol(reinterpret_cast<const unsigned char *>(IP_PROTO_TAG_HTTP_1_1.data()), |
| IP_PROTO_TAG_HTTP_1_1.size())) { |
| return EVENT_ERROR; |
| } |
| } |
| |
| { |
| const unsigned char *proto = nullptr; |
| unsigned len = 0; |
| |
| increment_ssl_version_metric(SSL_version(ssl)); |
| |
| // If it's possible to negotiate both NPN and ALPN, then ALPN |
| // is preferred since it is the server's preference. The server |
| // preference would not be meaningful if we let the client |
| // preference have priority. |
| SSL_get0_alpn_selected(ssl, &proto, &len); |
| if (len == 0) { |
| SSL_get0_next_proto_negotiated(ssl, &proto, &len); |
| } |
| |
| if (len) { |
| if (_tunnel_type == SNIRoutingType::NONE && !this->setSelectedProtocol(proto, len)) { |
| return EVENT_ERROR; |
| } |
| this->set_negotiated_protocol_id({reinterpret_cast<const char *>(proto), static_cast<size_t>(len)}); |
| |
| Debug("ssl", "client selected next protocol '%.*s'", len, proto); |
| } else { |
| Debug("ssl", "client did not select a next protocol"); |
| } |
| } |
| |
| #if TS_USE_TLS_ASYNC |
| if (SSLConfigParams::async_handshake_enabled) { |
| SSL_clear_mode(ssl, SSL_MODE_ASYNC); |
| if (async_ep.fd >= 0) { |
| async_ep.stop(); |
| } |
| } |
| #endif |
| return EVENT_DONE; |
| |
| case SSL_ERROR_WANT_CONNECT: |
| return SSL_HANDSHAKE_WANT_CONNECT; |
| |
| case SSL_ERROR_WANT_WRITE: |
| return SSL_HANDSHAKE_WANT_WRITE; |
| |
| case SSL_ERROR_WANT_READ: |
| return SSL_HANDSHAKE_WANT_READ; |
| #ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB |
| case SSL_ERROR_WANT_CLIENT_HELLO_CB: |
| return EVENT_CONT; |
| #endif |
| // This value is only defined in openssl has been patched to |
| // enable the sni callback to break out of the SSL_accept processing |
| #ifdef SSL_ERROR_WANT_SNI_RESOLVE |
| case SSL_ERROR_WANT_X509_LOOKUP: |
| return EVENT_CONT; |
| case SSL_ERROR_WANT_SNI_RESOLVE: |
| #elif SSL_ERROR_WANT_X509_LOOKUP |
| case SSL_ERROR_WANT_X509_LOOKUP: |
| #endif |
| #if defined(SSL_ERROR_WANT_SNI_RESOLVE) || defined(SSL_ERROR_WANT_X509_LOOKUP) |
| if (this->attributes == HttpProxyPort::TRANSPORT_BLIND_TUNNEL || SSL_HOOK_OP_TUNNEL == hookOpRequested) { |
| this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; |
| sslHandshakeStatus = SSL_HANDSHAKE_ONGOING; |
| return EVENT_CONT; |
| } else { |
| // Stopping for some other reason, perhaps loading certificate |
| return SSL_WAIT_FOR_HOOK; |
| } |
| #endif |
| |
| #if TS_USE_TLS_ASYNC |
| case SSL_ERROR_WANT_ASYNC: |
| SSL_INCREMENT_DYN_STAT(ssl_error_async); |
| return SSL_WAIT_FOR_ASYNC; |
| #endif |
| |
| case SSL_ERROR_WANT_ACCEPT: |
| return EVENT_CONT; |
| |
| case SSL_ERROR_SSL: { |
| SSL_CLR_ERR_INCR_DYN_STAT(this, ssl_error_ssl, "SSLNetVConnection::sslServerHandShakeEvent, SSL_ERROR_SSL errno=%d", errno); |
| return EVENT_ERROR; |
| } |
| |
| case SSL_ERROR_ZERO_RETURN: |
| return EVENT_ERROR; |
| case SSL_ERROR_SYSCALL: |
| return EVENT_ERROR; |
| default: |
| return EVENT_ERROR; |
| } |
| } |
| |
| int |
| SSLNetVConnection::sslClientHandShakeEvent(int &err) |
| { |
| ssl_error_t ssl_error; |
| |
| ink_assert(TLSBasicSupport::getInstance(ssl) == this); |
| |
| // Initialize properly for a client connection |
| if (sslHandshakeHookState == HANDSHAKE_HOOKS_PRE) { |
| if (this->pp_info.version != ProxyProtocolVersion::UNDEFINED) { |
| // Outbound PROXY Protocol |
| VIO &vio = this->write.vio; |
| int64_t ntodo = vio.ntodo(); |
| int64_t towrite = vio.buffer.reader()->read_avail(); |
| |
| if (ntodo > 0 && towrite > 0) { |
| MIOBufferAccessor &buf = vio.buffer; |
| int needs = 0; |
| int64_t total_written = 0; |
| int64_t r = super::load_buffer_and_write(towrite, buf, total_written, needs); |
| |
| if (total_written > 0) { |
| vio.ndone += total_written; |
| if (vio.ntodo() != 0) { |
| return SSL_WAIT_FOR_HOOK; |
| } |
| } |
| |
| if (r < 0) { |
| if (r == -EAGAIN || r == -ENOTCONN || -r == EINPROGRESS) { |
| return SSL_WAIT_FOR_HOOK; |
| } else { |
| return EVENT_ERROR; |
| } |
| } |
| } |
| } |
| |
| sslHandshakeHookState = HANDSHAKE_HOOKS_OUTBOUND_PRE; |
| } |
| |
| // Do outbound hook processing here |
| // Continue on if we are in the invoked state. The hook has not yet reenabled |
| if (sslHandshakeHookState == HANDSHAKE_HOOKS_OUTBOUND_PRE_INVOKE) { |
| return SSL_WAIT_FOR_HOOK; |
| } |
| |
| // Go do the preaccept hooks |
| if (sslHandshakeHookState == HANDSHAKE_HOOKS_OUTBOUND_PRE) { |
| SSL_INCREMENT_DYN_STAT(ssl_total_attempts_handshake_count_out_stat); |
| if (!curHook) { |
| Debug("ssl", "Initialize outbound connect curHook from NULL"); |
| curHook = ssl_hooks->get(TSSslHookInternalID(TS_VCONN_OUTBOUND_START_HOOK)); |
| } else { |
| curHook = curHook->next(); |
| } |
| // If no more hooks, carry on |
| if (nullptr != curHook) { |
| sslHandshakeHookState = HANDSHAKE_HOOKS_OUTBOUND_PRE_INVOKE; |
| ContWrapper::wrap(nh->mutex.get(), curHook->m_cont, TS_EVENT_VCONN_OUTBOUND_START, this); |
| return SSL_WAIT_FOR_HOOK; |
| } |
| } |
| |
| ssl_error = this->_ssl_connect(); |
| switch (ssl_error) { |
| case SSL_ERROR_NONE: |
| if (is_debug_tag_set("ssl")) { |
| X509 *cert = SSL_get_peer_certificate(ssl); |
| |
| Debug("ssl", "SSL client handshake completed successfully"); |
| |
| if (cert) { |
| debug_certificate_name("server certificate subject CN is", X509_get_subject_name(cert)); |
| debug_certificate_name("server certificate issuer CN is", X509_get_issuer_name(cert)); |
| X509_free(cert); |
| } |
| } |
| |
| // if the handshake is complete and write is enabled reschedule the write |
| if (closed == 0 && write.enabled) { |
| writeReschedule(nh); |
| } |
| |
| SSL_INCREMENT_DYN_STAT(ssl_total_success_handshake_count_out_stat); |
| |
| sslHandshakeStatus = SSL_HANDSHAKE_DONE; |
| return EVENT_DONE; |
| |
| case SSL_ERROR_WANT_WRITE: |
| Debug("ssl.error", "SSL_ERROR_WANT_WRITE"); |
| return SSL_HANDSHAKE_WANT_WRITE; |
| |
| case SSL_ERROR_WANT_READ: |
| Debug("ssl.error", "SSL_ERROR_WANT_READ"); |
| return SSL_HANDSHAKE_WANT_READ; |
| #ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB |
| case SSL_ERROR_WANT_CLIENT_HELLO_CB: |
| Debug("ssl.error", "SSL_ERROR_WANT_CLIENT_HELLO_CB"); |
| break; |
| #endif |
| case SSL_ERROR_WANT_X509_LOOKUP: |
| Debug("ssl.error", "SSL_ERROR_WANT_X509_LOOKUP"); |
| break; |
| |
| case SSL_ERROR_WANT_ACCEPT: |
| return SSL_HANDSHAKE_WANT_ACCEPT; |
| |
| case SSL_ERROR_WANT_CONNECT: |
| break; |
| |
| case SSL_ERROR_ZERO_RETURN: |
| Debug("ssl.error", "EOS"); |
| return EVENT_ERROR; |
| |
| case SSL_ERROR_SYSCALL: |
| err = errno; |
| SSL_INCREMENT_DYN_STAT(ssl_error_syscall); |
| Debug("ssl.error", "syscall"); |
| return EVENT_ERROR; |
| break; |
| |
| case SSL_ERROR_SSL: |
| default: { |
| err = (errno) ? errno : -ENET_SSL_CONNECT_FAILED; |
| char buf[512]; |
| unsigned long e = ERR_peek_last_error(); |
| ERR_error_string_n(e, buf, sizeof(buf)); |
| // FIXME -- This triggers a retry on cases of cert validation errors.... |
| SSL_CLR_ERR_INCR_DYN_STAT(this, ssl_error_ssl, "SSL_ERROR_SSL errno=%d", errno); |
| Debug("ssl.error", "SSL_ERROR_SSL"); |
| if (e) { |
| if (this->options.sni_servername) { |
| Debug("ssl.error", "SSL connection failed for '%s': %s", this->options.sni_servername.get(), buf); |
| } else { |
| char buff[INET6_ADDRSTRLEN]; |
| ats_ip_ntop(this->get_remote_addr(), buff, INET6_ADDRSTRLEN); |
| Debug("ssl.error", "SSL connection failed for '%s': %s", buff, buf); |
| } |
| } |
| return EVENT_ERROR; |
| } break; |
| } |
| return EVENT_CONT; |
| } |
| |
| void |
| SSLNetVConnection::reenable(NetHandler *nh, int event) |
| { |
| Debug("ssl", "Handshake reenable from state=%d", sslHandshakeHookState); |
| |
| // Mark as error to stop the Handshake |
| if (event == TS_EVENT_ERROR) { |
| sslHandshakeStatus = SSL_HANDSHAKE_ERROR; |
| } |
| |
| switch (sslHandshakeHookState) { |
| case HANDSHAKE_HOOKS_PRE_INVOKE: |
| sslHandshakeHookState = HANDSHAKE_HOOKS_PRE; |
| break; |
| case HANDSHAKE_HOOKS_OUTBOUND_PRE_INVOKE: |
| sslHandshakeHookState = HANDSHAKE_HOOKS_OUTBOUND_PRE; |
| break; |
| case HANDSHAKE_HOOKS_CLIENT_HELLO_INVOKE: |
| sslHandshakeHookState = HANDSHAKE_HOOKS_CLIENT_HELLO; |
| break; |
| case HANDSHAKE_HOOKS_CERT_INVOKE: |
| sslHandshakeHookState = HANDSHAKE_HOOKS_CERT; |
| break; |
| case HANDSHAKE_HOOKS_VERIFY_SERVER: |
| case HANDSHAKE_HOOKS_CLIENT_CERT: |
| break; |
| default: |
| break; |
| } |
| |
| // Reenabling from the handshake callback |
| // |
| // Originally, we would wait for the callback to go again to execute additional |
| // hooks, but since the callbacks are associated with the context and the context |
| // can be replaced by the plugin, it didn't seem reasonable to assume that the |
| // callback would be executed again. So we walk through the rest of the hooks |
| // here in the reenable. |
| if (curHook != nullptr) { |
| curHook = curHook->next(); |
| Debug("ssl", "iterate from reenable curHook=%p", curHook); |
| } |
| if (curHook != nullptr) { |
| // Invoke the hook and return, wait for next reenable |
| if (sslHandshakeHookState == HANDSHAKE_HOOKS_CLIENT_HELLO) { |
| sslHandshakeHookState = HANDSHAKE_HOOKS_CLIENT_HELLO_INVOKE; |
| curHook->invoke(TS_EVENT_SSL_CLIENT_HELLO, this); |
| } else if (sslHandshakeHookState == HANDSHAKE_HOOKS_CLIENT_CERT) { |
| sslHandshakeHookState = HANDSHAKE_HOOKS_CLIENT_CERT_INVOKE; |
| curHook->invoke(TS_EVENT_SSL_VERIFY_CLIENT, this); |
| } else if (sslHandshakeHookState == HANDSHAKE_HOOKS_CERT) { |
| sslHandshakeHookState = HANDSHAKE_HOOKS_CERT_INVOKE; |
| curHook->invoke(TS_EVENT_SSL_CERT, this); |
| } else if (sslHandshakeHookState == HANDSHAKE_HOOKS_SNI) { |
| curHook->invoke(TS_EVENT_SSL_SERVERNAME, this); |
| } else if (sslHandshakeHookState == HANDSHAKE_HOOKS_PRE) { |
| Debug("ssl", "Reenable preaccept"); |
| sslHandshakeHookState = HANDSHAKE_HOOKS_PRE_INVOKE; |
| ContWrapper::wrap(nh->mutex.get(), curHook->m_cont, TS_EVENT_VCONN_START, this); |
| } else if (sslHandshakeHookState == HANDSHAKE_HOOKS_OUTBOUND_PRE) { |
| Debug("ssl", "Reenable outbound connect"); |
| sslHandshakeHookState = HANDSHAKE_HOOKS_OUTBOUND_PRE_INVOKE; |
| ContWrapper::wrap(nh->mutex.get(), curHook->m_cont, TS_EVENT_VCONN_OUTBOUND_START, this); |
| } else if (sslHandshakeHookState == HANDSHAKE_HOOKS_DONE) { |
| if (this->get_context() == NET_VCONNECTION_OUT) { |
| ContWrapper::wrap(nh->mutex.get(), curHook->m_cont, TS_EVENT_VCONN_OUTBOUND_CLOSE, this); |
| } else { |
| ContWrapper::wrap(nh->mutex.get(), curHook->m_cont, TS_EVENT_VCONN_CLOSE, this); |
| } |
| } else if (sslHandshakeHookState == HANDSHAKE_HOOKS_VERIFY_SERVER) { |
| Debug("ssl", "ServerVerify"); |
| ContWrapper::wrap(nh->mutex.get(), curHook->m_cont, TS_EVENT_SSL_VERIFY_SERVER, this); |
| } |
| return; |
| } else { |
| // Move onto the "next" state |
| switch (this->sslHandshakeHookState) { |
| case HANDSHAKE_HOOKS_PRE: |
| case HANDSHAKE_HOOKS_PRE_INVOKE: |
| sslHandshakeHookState = HANDSHAKE_HOOKS_CLIENT_HELLO; |
| break; |
| case HANDSHAKE_HOOKS_CLIENT_HELLO: |
| case HANDSHAKE_HOOKS_CLIENT_HELLO_INVOKE: |
| sslHandshakeHookState = HANDSHAKE_HOOKS_SNI; |
| break; |
| case HANDSHAKE_HOOKS_SNI: |
| sslHandshakeHookState = HANDSHAKE_HOOKS_CERT; |
| break; |
| case HANDSHAKE_HOOKS_CERT: |
| case HANDSHAKE_HOOKS_CERT_INVOKE: |
| sslHandshakeHookState = HANDSHAKE_HOOKS_CLIENT_CERT; |
| break; |
| case HANDSHAKE_HOOKS_OUTBOUND_PRE: |
| case HANDSHAKE_HOOKS_OUTBOUND_PRE_INVOKE: |
| this->write.triggered = true; |
| this->write.enabled = true; |
| this->writeReschedule(nh); |
| sslHandshakeHookState = HANDSHAKE_HOOKS_DONE; |
| break; |
| case HANDSHAKE_HOOKS_CLIENT_CERT: |
| case HANDSHAKE_HOOKS_CLIENT_CERT_INVOKE: |
| sslHandshakeHookState = HANDSHAKE_HOOKS_DONE; |
| break; |
| case HANDSHAKE_HOOKS_VERIFY_SERVER: |
| sslHandshakeHookState = HANDSHAKE_HOOKS_DONE; |
| break; |
| default: |
| break; |
| } |
| Debug("ssl", "iterate from reenable curHook=%p %d", curHook, sslHandshakeHookState); |
| } |
| |
| this->readReschedule(nh); |
| } |
| |
| bool |
| SSLNetVConnection::callHooks(TSEvent eventId) |
| { |
| // Only dealing with the SNI/CERT hook so far. |
| ink_assert(eventId == TS_EVENT_SSL_CLIENT_HELLO || eventId == TS_EVENT_SSL_CERT || eventId == TS_EVENT_SSL_SERVERNAME || |
| eventId == TS_EVENT_SSL_VERIFY_SERVER || eventId == TS_EVENT_SSL_VERIFY_CLIENT || eventId == TS_EVENT_VCONN_CLOSE || |
| eventId == TS_EVENT_VCONN_OUTBOUND_CLOSE); |
| Debug("ssl", "sslHandshakeHookState=%d eventID=%d", this->sslHandshakeHookState, eventId); |
| |
| // Move state if it is appropriate |
| if (eventId == TS_EVENT_VCONN_CLOSE) { |
| // Regardless of state, if the connection is closing, then transition to |
| // the DONE state. This will trigger us to call the appropriate cleanup |
| // routines. |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_DONE; |
| } else { |
| switch (this->sslHandshakeHookState) { |
| case HANDSHAKE_HOOKS_PRE: |
| case HANDSHAKE_HOOKS_OUTBOUND_PRE: |
| if (eventId == TS_EVENT_SSL_CLIENT_HELLO) { |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_CLIENT_HELLO; |
| } else if (eventId == TS_EVENT_SSL_SERVERNAME) { |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_SNI; |
| } else if (eventId == TS_EVENT_SSL_VERIFY_SERVER) { |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_VERIFY_SERVER; |
| } else if (eventId == TS_EVENT_SSL_CERT) { |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_CERT; |
| } |
| break; |
| case HANDSHAKE_HOOKS_CLIENT_HELLO: |
| if (eventId == TS_EVENT_SSL_SERVERNAME) { |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_SNI; |
| } else if (eventId == TS_EVENT_SSL_CERT) { |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_CERT; |
| } |
| break; |
| case HANDSHAKE_HOOKS_SNI: |
| if (eventId == TS_EVENT_SSL_CERT) { |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_CERT; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // Look for hooks associated with the event |
| switch (this->sslHandshakeHookState) { |
| case HANDSHAKE_HOOKS_CLIENT_HELLO: |
| case HANDSHAKE_HOOKS_CLIENT_HELLO_INVOKE: |
| if (!curHook) { |
| curHook = ssl_hooks->get(TSSslHookInternalID(TS_SSL_CLIENT_HELLO_HOOK)); |
| } else { |
| curHook = curHook->next(); |
| } |
| if (curHook == nullptr) { |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_SNI; |
| } else { |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_CLIENT_HELLO_INVOKE; |
| } |
| break; |
| case HANDSHAKE_HOOKS_VERIFY_SERVER: |
| // The server verify event addresses ATS to origin handshake |
| // All the other events are for client to ATS |
| if (!curHook) { |
| curHook = ssl_hooks->get(TSSslHookInternalID(TS_SSL_VERIFY_SERVER_HOOK)); |
| } else { |
| curHook = curHook->next(); |
| } |
| break; |
| case HANDSHAKE_HOOKS_SNI: |
| if (!curHook) { |
| curHook = ssl_hooks->get(TSSslHookInternalID(TS_SSL_SERVERNAME_HOOK)); |
| } else { |
| curHook = curHook->next(); |
| } |
| if (!curHook) { |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_CERT; |
| } |
| break; |
| case HANDSHAKE_HOOKS_CERT: |
| case HANDSHAKE_HOOKS_CERT_INVOKE: |
| if (!curHook) { |
| curHook = ssl_hooks->get(TSSslHookInternalID(TS_SSL_CERT_HOOK)); |
| } else { |
| curHook = curHook->next(); |
| } |
| if (curHook == nullptr) { |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_CLIENT_CERT; |
| } else { |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_CERT_INVOKE; |
| } |
| break; |
| case HANDSHAKE_HOOKS_CLIENT_CERT: |
| case HANDSHAKE_HOOKS_CLIENT_CERT_INVOKE: |
| if (!curHook) { |
| curHook = ssl_hooks->get(TSSslHookInternalID(TS_SSL_VERIFY_CLIENT_HOOK)); |
| } else { |
| curHook = curHook->next(); |
| } |
| // fallthrough |
| case HANDSHAKE_HOOKS_DONE: |
| case HANDSHAKE_HOOKS_OUTBOUND_PRE: |
| if (eventId == TS_EVENT_VCONN_CLOSE) { |
| sslHandshakeHookState = HANDSHAKE_HOOKS_DONE; |
| if (curHook == nullptr) { |
| curHook = ssl_hooks->get(TSSslHookInternalID(TS_VCONN_CLOSE_HOOK)); |
| } else { |
| curHook = curHook->next(); |
| } |
| } else if (eventId == TS_EVENT_VCONN_OUTBOUND_CLOSE) { |
| sslHandshakeHookState = HANDSHAKE_HOOKS_DONE; |
| if (curHook == nullptr) { |
| curHook = ssl_hooks->get(TSSslHookInternalID(TS_VCONN_OUTBOUND_CLOSE_HOOK)); |
| } else { |
| curHook = curHook->next(); |
| } |
| } |
| break; |
| default: |
| curHook = nullptr; |
| this->sslHandshakeHookState = HANDSHAKE_HOOKS_DONE; |
| return true; |
| } |
| |
| Debug("ssl", "iterated to curHook=%p", curHook); |
| |
| bool reenabled = true; |
| |
| if (SSL_HOOK_OP_TUNNEL == hookOpRequested) { |
| this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; |
| // Don't mark the handshake as complete yet, |
| // Will be checking for that flag not being set after |
| // we get out of this callback, and then will shuffle |
| // over the buffered handshake packets to the O.S. |
| // sslHandShakeComplete = 1; |
| return reenabled; |
| } |
| |
| if (curHook != nullptr) { |
| WEAK_SCOPED_MUTEX_LOCK(lock, curHook->m_cont->mutex, this_ethread()); |
| curHook->invoke(eventId, this); |
| reenabled = |
| (this->sslHandshakeHookState != HANDSHAKE_HOOKS_CERT_INVOKE && this->sslHandshakeHookState != HANDSHAKE_HOOKS_PRE_INVOKE && |
| this->sslHandshakeHookState != HANDSHAKE_HOOKS_CLIENT_HELLO_INVOKE); |
| Debug("ssl", "Called hook on state=%d reenabled=%d", sslHandshakeHookState, reenabled); |
| } |
| |
| return reenabled; |
| } |
| |
| int |
| SSLNetVConnection::populate(Connection &con, Continuation *c, void *arg) |
| { |
| int retval = super::populate(con, c, arg); |
| if (retval != EVENT_DONE) { |
| return retval; |
| } |
| // Add in the SSL data |
| this->ssl = static_cast<SSL *>(arg); |
| // Maybe bring over the stats? |
| |
| sslHandshakeStatus = SSL_HANDSHAKE_DONE; |
| this->_bindSSLObject(); |
| return EVENT_DONE; |
| } |
| |
| void |
| SSLNetVConnection::increment_ssl_version_metric(int version) const |
| { |
| switch (version) { |
| case SSL3_VERSION: |
| SSL_INCREMENT_DYN_STAT(ssl_total_sslv3); |
| break; |
| case TLS1_VERSION: |
| SSL_INCREMENT_DYN_STAT(ssl_total_tlsv1); |
| break; |
| case TLS1_1_VERSION: |
| SSL_INCREMENT_DYN_STAT(ssl_total_tlsv11); |
| break; |
| case TLS1_2_VERSION: |
| SSL_INCREMENT_DYN_STAT(ssl_total_tlsv12); |
| break; |
| #ifdef TLS1_3_VERSION |
| case TLS1_3_VERSION: |
| SSL_INCREMENT_DYN_STAT(ssl_total_tlsv13); |
| break; |
| #endif |
| default: |
| Debug("ssl", "Unrecognized SSL version %d", version); |
| break; |
| } |
| } |
| |
| std::string_view |
| SSLNetVConnection::map_tls_protocol_to_tag(const char *proto_string) const |
| { |
| std::string_view retval{"tls/?.?"sv}; // return this if the protocol lookup doesn't work. |
| |
| if (proto_string) { |
| // openSSL guarantees the case of the protocol string. |
| if (proto_string[0] == 'T' && proto_string[1] == 'L' && proto_string[2] == 'S' && proto_string[3] == 'v' && |
| proto_string[4] == '1') { |
| if (proto_string[5] == 0) { |
| retval = IP_PROTO_TAG_TLS_1_0; |
| } else if (proto_string[5] == '.' && proto_string[7] == 0) { |
| switch (proto_string[6]) { |
| case '1': |
| retval = IP_PROTO_TAG_TLS_1_1; |
| break; |
| case '2': |
| retval = IP_PROTO_TAG_TLS_1_2; |
| break; |
| case '3': |
| retval = IP_PROTO_TAG_TLS_1_3; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| } |
| return retval; |
| } |
| |
| int |
| SSLNetVConnection::populate_protocol(std::string_view *results, int n) const |
| { |
| int retval = 0; |
| if (n > retval) { |
| results[retval] = map_tls_protocol_to_tag(this->get_tls_protocol_name()); |
| if (!results[retval].empty()) { |
| ++retval; |
| } |
| if (n > retval) { |
| retval += super::populate_protocol(results + retval, n - retval); |
| } |
| } |
| return retval; |
| } |
| |
| const char * |
| SSLNetVConnection::protocol_contains(std::string_view prefix) const |
| { |
| const char *retval = nullptr; |
| std::string_view tag = map_tls_protocol_to_tag(this->get_tls_protocol_name()); |
| if (prefix.size() <= tag.size() && strncmp(tag.data(), prefix.data(), prefix.size()) == 0) { |
| retval = tag.data(); |
| } else { |
| retval = super::protocol_contains(prefix); |
| } |
| return retval; |
| } |
| |
| void |
| SSLNetVConnection::_fire_ssl_servername_event() |
| { |
| this->callHooks(TS_EVENT_SSL_SERVERNAME); |
| } |
| |
| void |
| SSLNetVConnection::set_ca_cert_file(std::string_view file, std::string_view dir) |
| { |
| if (file.size()) { |
| char *n = new char[file.size() + 1]; |
| std::memcpy(n, file.data(), file.size()); |
| n[file.size()] = '\0'; |
| _ca_cert_file.reset(n); |
| } |
| if (dir.size()) { |
| char *n = new char[dir.size() + 1]; |
| std::memcpy(n, dir.data(), dir.size()); |
| n[dir.size()] = '\0'; |
| _ca_cert_dir.reset(n); |
| } |
| } |
| |
| void * |
| SSLNetVConnection::_prepareForMigration() |
| { |
| SSL *save_ssl = this->ssl; |
| |
| this->_unbindSSLObject(); |
| this->ssl = nullptr; |
| |
| return save_ssl; |
| } |
| |
| NetProcessor * |
| SSLNetVConnection::_getNetProcessor() |
| { |
| return &sslNetProcessor; |
| } |
| |
| ssl_curve_id |
| SSLNetVConnection::_get_tls_curve() const |
| { |
| if (getSSLSessionCacheHit()) { |
| return getSSLCurveNID(); |
| } else { |
| return SSLGetCurveNID(ssl); |
| } |
| } |
| |
| ssl_error_t |
| SSLNetVConnection::_ssl_accept() |
| { |
| ERR_clear_error(); |
| |
| int ret = 0; |
| int ssl_error = SSL_ERROR_NONE; |
| |
| #if TS_HAS_TLS_EARLY_DATA |
| if (SSLConfigParams::server_max_early_data > 0 && !this->early_data_finish) { |
| size_t nread; |
| |
| while (true) { |
| IOBufferBlock *block = new_IOBufferBlock(); |
| block->alloc(BUFFER_SIZE_INDEX_16K); |
| ret = SSL_read_early_data(ssl, block->buf(), index_to_buffer_size(BUFFER_SIZE_INDEX_16K), &nread); |
| |
| if (ret == SSL_READ_EARLY_DATA_ERROR) { |
| Debug("ssl_early_data", "SSL_READ_EARLY_DATA_ERROR"); |
| block->free(); |
| break; |
| } else { |
| if (nread > 0) { |
| if (this->early_data_buf == nullptr) { |
| this->early_data_buf = new_MIOBuffer(BUFFER_SIZE_INDEX_16K); |
| this->early_data_reader = this->early_data_buf->alloc_reader(); |
| } |
| block->fill(nread); |
| this->early_data_buf->append_block(block); |
| SSL_INCREMENT_DYN_STAT(ssl_early_data_received_count); |
| |
| if (is_debug_tag_set("ssl_early_data_show_received")) { |
| std::string early_data_str(reinterpret_cast<char *>(block->buf()), nread); |
| Debug("ssl_early_data_show_received", "Early data buffer: \n%s", early_data_str.c_str()); |
| } |
| } else { |
| block->free(); |
| } |
| |
| if (ret == SSL_READ_EARLY_DATA_FINISH) { |
| this->early_data_finish = true; |
| Debug("ssl_early_data", "SSL_READ_EARLY_DATA_FINISH: size = %lu", nread); |
| |
| if (this->early_data_reader == nullptr || this->early_data_reader->read_avail() == 0) { |
| Debug("ssl_early_data", "no data in early data buffer"); |
| ERR_clear_error(); |
| ret = SSL_accept(ssl); |
| } |
| break; |
| } |
| Debug("ssl_early_data", "SSL_READ_EARLY_DATA_SUCCESS: size = %lu", nread); |
| } |
| } |
| } else { |
| ret = SSL_accept(ssl); |
| } |
| #else |
| ret = SSL_accept(ssl); |
| #endif |
| |
| if (ret > 0) { |
| return SSL_ERROR_NONE; |
| } |
| ssl_error = SSL_get_error(ssl, ret); |
| if (ssl_error == SSL_ERROR_SSL && is_debug_tag_set("ssl.error.accept")) { |
| char buf[512]; |
| unsigned long e = ERR_peek_last_error(); |
| ERR_error_string_n(e, buf, sizeof(buf)); |
| Debug("ssl.error.accept", "SSL accept returned %d, ssl_error=%d, ERR_get_error=%ld (%s)", ret, ssl_error, e, buf); |
| } |
| |
| return ssl_error; |
| } |
| |
| ssl_error_t |
| SSLNetVConnection::_ssl_connect() |
| { |
| ERR_clear_error(); |
| |
| SSL_SESSION *sess = SSL_get_session(ssl); |
| if (!sess && SSLConfigParams::origin_session_cache == 1 && SSLConfigParams::origin_session_cache_size > 0) { |
| std::string sni_addr = get_sni_addr(ssl); |
| if (!sni_addr.empty()) { |
| std::string lookup_key; |
| ts::bwprint(lookup_key, "{}:{}:{}", sni_addr.c_str(), SSL_get_SSL_CTX(ssl), get_verify_str(ssl)); |
| |
| Debug("ssl.origin_session_cache", "origin session cache lookup key = %s", lookup_key.c_str()); |
| |
| sess = this->getOriginSession(ssl, lookup_key); |
| if (sess) { |
| SSL_set_session(ssl, sess); |
| } |
| } |
| } |
| |
| int ret = SSL_connect(ssl); |
| |
| if (ret > 0) { |
| if (sess && SSL_session_reused(ssl)) { |
| SSL_INCREMENT_DYN_STAT(ssl_origin_session_reused_count); |
| if (is_debug_tag_set("ssl.origin_session_cache")) { |
| Debug("ssl.origin_session_cache", "reused session to origin server = %p", sess); |
| } |
| } else { |
| if (is_debug_tag_set("ssl.origin_session_cache")) { |
| Debug("ssl.origin_session_cache", "new session to origin server = %p", sess); |
| } |
| } |
| return SSL_ERROR_NONE; |
| } |
| int ssl_error = SSL_get_error(ssl, ret); |
| if (ssl_error == SSL_ERROR_SSL && is_debug_tag_set("ssl.error.connect")) { |
| char buf[512]; |
| unsigned long e = ERR_peek_last_error(); |
| ERR_error_string_n(e, buf, sizeof(buf)); |
| Debug("ssl.error.connect", "SSL connect returned %d, ssl_error=%d, ERR_get_error=%ld (%s)", ret, ssl_error, e, buf); |
| } |
| |
| return ssl_error; |
| } |
| |
| ssl_error_t |
| SSLNetVConnection::_ssl_write_buffer(const void *buf, int64_t nbytes, int64_t &nwritten) |
| { |
| nwritten = 0; |
| |
| if (unlikely(nbytes == 0)) { |
| return SSL_ERROR_NONE; |
| } |
| ERR_clear_error(); |
| |
| int ret; |
| #if TS_HAS_TLS_EARLY_DATA |
| if (SSL_version(ssl) >= TLS1_3_VERSION) { |
| if (SSL_is_init_finished(ssl)) { |
| ret = SSL_write(ssl, buf, static_cast<int>(nbytes)); |
| } else { |
| size_t nwrite; |
| ret = SSL_write_early_data(ssl, buf, static_cast<size_t>(nbytes), &nwrite); |
| if (ret == 1) { |
| ret = nwrite; |
| } |
| } |
| } else { |
| ret = SSL_write(ssl, buf, static_cast<int>(nbytes)); |
| } |
| #else |
| ret = SSL_write(ssl, buf, static_cast<int>(nbytes)); |
| #endif |
| |
| if (ret > 0) { |
| nwritten = ret; |
| BIO *bio = SSL_get_wbio(ssl); |
| if (bio != nullptr) { |
| (void)BIO_flush(bio); |
| } |
| return SSL_ERROR_NONE; |
| } |
| int ssl_error = SSL_get_error(ssl, ret); |
| if (ssl_error == SSL_ERROR_SSL && is_debug_tag_set("ssl.error.write")) { |
| char tempbuf[512]; |
| unsigned long e = ERR_peek_last_error(); |
| ERR_error_string_n(e, tempbuf, sizeof(tempbuf)); |
| Debug("ssl.error.write", "SSL write returned %d, ssl_error=%d, ERR_get_error=%ld (%s)", ret, ssl_error, e, tempbuf); |
| } |
| return ssl_error; |
| } |
| |
| ssl_error_t |
| SSLNetVConnection::_ssl_read_buffer(void *buf, int64_t nbytes, int64_t &nread) |
| { |
| nread = 0; |
| |
| if (unlikely(nbytes == 0)) { |
| return SSL_ERROR_NONE; |
| } |
| ERR_clear_error(); |
| |
| #if TS_HAS_TLS_EARLY_DATA |
| if (SSL_version(ssl) >= TLS1_3_VERSION) { |
| int64_t early_data_len = 0; |
| if (this->early_data_reader != nullptr) { |
| early_data_len = this->early_data_reader->read_avail(); |
| } |
| |
| if (early_data_len > 0) { |
| Debug("ssl_early_data", "Reading from early data buffer."); |
| this->read_from_early_data += this->early_data_reader->read(buf, nbytes < early_data_len ? nbytes : early_data_len); |
| |
| if (nbytes < early_data_len) { |
| nread = nbytes; |
| } else { |
| nread = early_data_len; |
| } |
| |
| return SSL_ERROR_NONE; |
| } |
| |
| if (SSLConfigParams::server_max_early_data > 0 && !this->early_data_finish) { |
| Debug("ssl_early_data", "More early data to read."); |
| ssl_error_t ssl_error = SSL_ERROR_NONE; |
| size_t read_bytes = 0; |
| |
| int ret = SSL_read_early_data(ssl, buf, static_cast<size_t>(nbytes), &read_bytes); |
| |
| if (ret == SSL_READ_EARLY_DATA_ERROR) { |
| Debug("ssl_early_data", "SSL_READ_EARLY_DATA_ERROR"); |
| ssl_error = SSL_get_error(ssl, ret); |
| Debug("ssl_early_data", "Error reading early data: %s", ERR_error_string(ERR_get_error(), nullptr)); |
| } else { |
| if ((nread = read_bytes) > 0) { |
| this->read_from_early_data += read_bytes; |
| SSL_INCREMENT_DYN_STAT(ssl_early_data_received_count); |
| if (is_debug_tag_set("ssl_early_data_show_received")) { |
| std::string early_data_str(reinterpret_cast<char *>(buf), nread); |
| Debug("ssl_early_data_show_received", "Early data buffer: \n%s", early_data_str.c_str()); |
| } |
| } |
| |
| if (ret == SSL_READ_EARLY_DATA_FINISH) { |
| this->early_data_finish = true; |
| Debug("ssl_early_data", "SSL_READ_EARLY_DATA_FINISH: size = %" PRId64, nread); |
| } else { |
| Debug("ssl_early_data", "SSL_READ_EARLY_DATA_SUCCESS: size = %" PRId64, nread); |
| } |
| } |
| |
| return ssl_error; |
| } |
| } |
| #endif |
| |
| int ret = SSL_read(ssl, buf, static_cast<int>(nbytes)); |
| if (ret > 0) { |
| nread = ret; |
| return SSL_ERROR_NONE; |
| } |
| int ssl_error = SSL_get_error(ssl, ret); |
| if (ssl_error == SSL_ERROR_SSL && is_debug_tag_set("ssl.error.read")) { |
| char tempbuf[512]; |
| unsigned long e = ERR_peek_last_error(); |
| ERR_error_string_n(e, tempbuf, sizeof(tempbuf)); |
| Debug("ssl.error.read", "SSL read returned %d, ssl_error=%d, ERR_get_error=%ld (%s)", ret, ssl_error, e, tempbuf); |
| } |
| |
| return ssl_error; |
| } |