| /** @file | |
| A brief file description | |
| @par 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 "BIO_fastopen.h" | |
| #include "P_UnixNet.h" | |
| #include "P_UnixNetVConnection.h" | |
| #include "SSLStats.h" | |
| #include "P_Net.h" | |
| #include "P_SSLUtils.h" | |
| #include "ts/ats_probe.h" | |
| #include "P_SSLNextProtocolSet.h" | |
| #include "P_SSLConfig.h" | |
| #include "P_SSLClientUtils.h" | |
| #include "P_SSLNetVConnection.h" | |
| #include "P_UnixNetProcessor.h" | |
| #include "iocore/net/NetHandler.h" | |
| #include "iocore/net/NetVConnection.h" | |
| #include "iocore/net/ProxyProtocol.h" | |
| #include "iocore/net/SSLDiags.h" | |
| #include "iocore/net/SSLSNIConfig.h" | |
| #include "iocore/net/SSLTypes.h" | |
| #include "iocore/net/TLSALPNSupport.h" | |
| #include "tscore/ink_config.h" | |
| #include "tscore/Layout.h" | |
| #include "tscore/InkErrno.h" | |
| #include "tscore/LogMessage.h" | |
| #include "tscore/TSSystemState.h" | |
| #include <netinet/in.h> | |
| #include <string> | |
| #include <cstring> | |
| #include <utility> | |
| #if TS_USE_TLS_ASYNC | |
| #include <openssl/async.h> | |
| #endif | |
| using namespace std::literals; | |
| #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 | |
| #define SSL_RESTART 13 | |
| ClassAllocator<SSLNetVConnection, false> sslNetVCAllocator("sslNetVCAllocator"); | |
| namespace | |
| { | |
| DbgCtl dbg_ctl_ssl_early_data{"ssl_early_data"}; | |
| DbgCtl dbg_ctl_ssl_early_data_show_received{"ssl_early_data_show_received"}; | |
| DbgCtl dbg_ctl_ssl{"ssl"}; | |
| DbgCtl dbg_ctl_v_ssl{"v_ssl"}; | |
| DbgCtl dbg_ctl_ssl_error{"ssl.error"}; | |
| DbgCtl dbg_ctl_ssl_error_accept{"ssl.error.accept"}; | |
| DbgCtl dbg_ctl_ssl_error_connect{"ssl.error.connect"}; | |
| DbgCtl dbg_ctl_ssl_error_write{"ssl.error.write"}; | |
| DbgCtl dbg_ctl_ssl_error_read{"ssl.error.read"}; | |
| DbgCtl dbg_ctl_ssl_shutdown{"ssl-shutdown"}; | |
| DbgCtl dbg_ctl_ssl_alpn{"ssl_alpn"}; | |
| DbgCtl dbg_ctl_ssl_origin_session_cache{"ssl.origin_session_cache"}; | |
| DbgCtl dbg_ctl_proxyprotocol{"proxyprotocol"}; | |
| const char * | |
| resolve_client_ca_cert_path(const SSLConfigParams *params, const char *path, std::string &storage) | |
| { | |
| if (path == nullptr) { | |
| return params->clientCACertPath; | |
| } | |
| storage = Layout::get()->relative_to(Layout::get()->prefix, path); | |
| return storage.c_str(); | |
| } | |
| } // 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); | |
| BIO_fastopen_set_dest_addr(bio, | |
| this->options.f_tcp_fastopen ? this->get_remote_addr() : static_cast<const sockaddr *>(nullptr)); | |
| SSL_set_bio(ssl, bio, bio); | |
| } else { | |
| this->initialize_handshake_buffers(); | |
| BIO *rbio = BIO_new(BIO_s_mem()); | |
| BIO *wbio = BIO_new_socket(this->get_socket(), BIO_NOCLOSE); | |
| BIO_set_mem_eof_return(wbio, -1); | |
| SSL_set_bio(ssl, rbio, wbio); | |
| #if TS_HAS_TLS_EARLY_DATA | |
| update_early_data_config(ssl, SSLConfigParams::server_max_early_data, SSLConfigParams::server_recv_max_early_data); | |
| #endif | |
| } | |
| this->_bindSSLObject(); | |
| } | |
| } | |
| void | |
| SSLNetVConnection::_bindSSLObject() | |
| { | |
| SSLNetVCAttach(this->ssl, this); | |
| TLSBasicSupport::bind(this->ssl, this); | |
| TLSEventSupport::bind(this->ssl, this); | |
| ALPNSupport::bind(this->ssl, this); | |
| TLSSessionResumptionSupport::bind(this->ssl, this); | |
| TLSSNISupport::bind(this->ssl, this); | |
| TLSEarlyDataSupport::bind(this->ssl, this); | |
| TLSTunnelSupport::bind(this->ssl, this); | |
| TLSCertSwitchSupport::bind(this->ssl, this); | |
| } | |
| void | |
| SSLNetVConnection::_unbindSSLObject() | |
| { | |
| SSLNetVCDetach(this->ssl); | |
| TLSBasicSupport::unbind(this->ssl); | |
| TLSEventSupport::unbind(this->ssl); | |
| ALPNSupport::unbind(this->ssl); | |
| TLSSessionResumptionSupport::unbind(this->ssl); | |
| TLSSNISupport::unbind(this->ssl); | |
| TLSEarlyDataSupport::unbind(this->ssl); | |
| TLSTunnelSupport::unbind(this->ssl); | |
| TLSCertSwitchSupport::unbind(this->ssl); | |
| } | |
| static void | |
| debug_certificate_name(const char *msg, const 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); | |
| Dbg(dbg_ctl_ssl, "%s %.*s", msg, static_cast<int>(len), ptr); | |
| } | |
| BIO_free(bio); | |
| } | |
| int | |
| SSLNetVConnection::_ssl_read_from_net(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; | |
| } | |
| Dbg(dbg_ctl_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); | |
| Dbg(dbg_ctl_ssl, "nread=%" PRId64, nread); | |
| switch (sslErr) { | |
| case SSL_ERROR_NONE: | |
| #if DEBUG | |
| { | |
| static DbgCtl dbg_ctl{"ssl_buff"}; | |
| SSLDebugBufferPrint(dbg_ctl, 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(); | |
| } | |
| break; | |
| case SSL_ERROR_WANT_WRITE: | |
| event = SSL_WRITE_WOULD_BLOCK; | |
| Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WOULD_BLOCK(write)"); | |
| break; | |
| case SSL_ERROR_WANT_READ: | |
| event = SSL_READ_WOULD_BLOCK; | |
| Dbg(dbg_ctl_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; | |
| Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WOULD_BLOCK(read/client hello cb)"); | |
| break; | |
| #endif | |
| case SSL_ERROR_WANT_X509_LOOKUP: | |
| event = SSL_READ_WOULD_BLOCK; | |
| Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WOULD_BLOCK(read/x509 lookup)"); | |
| break; | |
| case SSL_ERROR_SYSCALL: | |
| if (nread != 0) { | |
| // not EOF | |
| Metrics::Counter::increment(ssl_rsb.error_syscall); | |
| event = SSL_READ_ERROR; | |
| ret = errno; | |
| Dbg(dbg_ctl_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; | |
| Dbg(dbg_ctl_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; | |
| SSLVCDebug(this, "errno=%d", errno); | |
| Metrics::Counter::increment(ssl_rsb.error_ssl); | |
| } break; | |
| } // switch | |
| } // while | |
| if (bytes_read > 0) { | |
| Dbg(dbg_ctl_ssl, "bytes_read=%" PRId64, bytes_read); | |
| s->vio.ndone += bytes_read; | |
| // Decrypted application bytes, to match write_bytes (also plaintext for TLS). | |
| Metrics::Counter::increment(net_rsb.read_bytes, bytes_read); | |
| Metrics::Counter::increment(net_rsb.read_bytes_count); | |
| this->netActivity(); | |
| 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) { | |
| Dbg(dbg_ctl_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 = this->con.sock.read(buffer, buf_len); | |
| Metrics::Counter::increment(net_rsb.calls_to_read); | |
| total_read += rattempted; | |
| Dbg(dbg_ctl_ssl, "read_raw_data r=%" PRId64 " rattempted=%" PRId64 " total_read=%" PRId64 " fd=%d", r, rattempted, total_read, | |
| con.sock.get_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; | |
| } | |
| } | |
| if (!this->haveCheckedProxyProtocol) { | |
| // The PROXY Protocol, by spec, is designed to require only the first TCP packet of bytes | |
| // because it is under typical MTU. So we only need to perform the following inspection on the | |
| // first packet. | |
| this->haveCheckedProxyProtocol = true; | |
| swoc::IPRangeSet *pp_ipmap; | |
| pp_ipmap = SSLConfigParams::proxy_protocol_ip_addrs; | |
| if (this->get_is_proxy_protocol() && this->get_proxy_protocol_version() == ProxyProtocolVersion::UNDEFINED) { | |
| Dbg(dbg_ctl_proxyprotocol, "proxy protocol is enabled on this port"); | |
| if (this->has_proxy_protocol_preface(buffer, r)) { | |
| if (pp_ipmap->count() > 0) { | |
| Dbg(dbg_ctl_proxyprotocol, "proxy protocol has a configured allowlist of trusted IPs - checking"); | |
| // Using get_remote_addr() will return the ip of the | |
| // proxy source IP, not the Proxy Protocol client ip. | |
| if (!pp_ipmap->contains(swoc::IPAddr(get_remote_addr()))) { | |
| Dbg(dbg_ctl_proxyprotocol, "Source 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]; | |
| Dbg(dbg_ctl_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 { | |
| Dbg(dbg_ctl_proxyprotocol, "proxy protocol DOES NOT have a configured allowlist of trusted IPs but " | |
| "proxy protocol is enabled on this port - processing all connections with Proxy Protocol " | |
| "headers"); | |
| } | |
| auto const stored_r = r; | |
| if (this->has_proxy_protocol(buffer, &r)) { | |
| Dbg(dbg_ctl_proxyprotocol, "ssl has proxy protocol header"); | |
| if (dbg_ctl_proxyprotocol.on()) { | |
| sockaddr const *src_addr = this->get_proxy_protocol_src_addr(); | |
| sockaddr const *dst_addr = this->get_proxy_protocol_dst_addr(); | |
| if (src_addr != nullptr && dst_addr != nullptr) { | |
| IpEndpoint src; | |
| src.sa = *src_addr; | |
| IpEndpoint dst; | |
| dst.sa = *dst_addr; | |
| ip_port_text_buffer src_ipb, dst_ipb; | |
| ats_ip_nptop(&src, src_ipb, sizeof(src_ipb)); | |
| ats_ip_nptop(&dst, dst_ipb, sizeof(dst_ipb)); | |
| DbgPrint(dbg_ctl_proxyprotocol, "ssl proxy protocol v%d header parsed: src=[%s] dst=[%s]", | |
| static_cast<int>(this->get_proxy_protocol_version()), src_ipb, dst_ipb); | |
| } else { | |
| DbgPrint(dbg_ctl_proxyprotocol, "ssl proxy protocol v%d header parsed without address information", | |
| static_cast<int>(this->get_proxy_protocol_version())); | |
| } | |
| } | |
| } else { | |
| Dbg(dbg_ctl_proxyprotocol, "proxy protocol preface was present, but Proxy Protocol header could not be parsed"); | |
| r = stored_r; | |
| } | |
| } else { | |
| Dbg(dbg_ctl_proxyprotocol, "proxy protocol was enabled, but Proxy Protocol header was not present"); | |
| } | |
| } | |
| } // end of Proxy Protocol processing | |
| proxy_protocol_bypass: | |
| if (r > 0) { | |
| this->handShakeBuffer->fill(r); | |
| auto const total_chain_size = this->handShakeReader->read_avail(); | |
| this->handShakeBioStored = total_chain_size; | |
| char *buffer_for_bio = this->_getCoalescedHandShakeBuffer(total_chain_size); | |
| // Sets up the buffer as a read only bio target | |
| // Must be reset on each read | |
| BIO *rbio = BIO_new_mem_buf(buffer_for_bio, this->handShakeBioStored); | |
| BIO_set_mem_eof_return(rbio, -1); | |
| SSL_set0_rbio(this->ssl, rbio); | |
| } else { | |
| this->handShakeBioStored = 0; | |
| } | |
| Dbg(dbg_ctl_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) { | |
| Metrics::Counter::increment(net_rsb.calls_to_read_nodata); | |
| } | |
| } | |
| 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; | |
| // OpenSSL 4 does not report EOF for an empty memory BIO configured to retry reads. | |
| if (this->handShakeReader != nullptr && BIO_ctrl_pending(SSL_get_rbio(this->ssl)) == 0) { | |
| Dbg(dbg_ctl_ssl, "Consuming handShakeBioStored=%d bytes from the handshake reader", this->handShakeBioStored); | |
| this->handShakeReader->consume(this->handShakeBioStored); | |
| this->handShakeBioStored = 0; | |
| // Load up the next block if present | |
| if (this->handShakeReader->is_read_avail_more_than(0)) { | |
| auto const total_chain_size = this->handShakeReader->read_avail(); | |
| this->handShakeBioStored = total_chain_size; | |
| char *buffer_for_bio = this->_getCoalescedHandShakeBuffer(total_chain_size); | |
| Dbg(dbg_ctl_ssl, "Adding %d bytes to the ssl rbio", this->handShakeBioStored); | |
| // Sets up the buffer as a read only bio target | |
| // Must be reset on each read | |
| BIO *rbio = BIO_new_mem_buf(buffer_for_bio, 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)) { | |
| Dbg(dbg_ctl_ssl, "No other bytes in the handshake reader, moving to socket rbio"); | |
| BIO *rbio = BIO_new_socket(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) | |
| { | |
| 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); | |
| return; | |
| } | |
| MUTEX_TRY_LOCK(lock, s->vio.mutex, nh->thread); | |
| 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); | |
| return; | |
| } | |
| // If the key renegotiation failed it's over, just signal the error and finish. | |
| if (sslClientRenegotiationAbort == true) { | |
| this->read.triggered = 0; | |
| this->_readSignalError(nh, -ENET_SSL_FAILED); | |
| Dbg(dbg_ctl_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 (ret == SSL_RESTART) { | |
| // VC migrated into a new object | |
| // Just give up and go home. Events should trigger on the new vc | |
| Dbg(dbg_ctl_ssl, "Restart for allow plain"); | |
| return; | |
| } | |
| // If we have flipped to blind tunnel, don't read ahead. We check for a | |
| // non-error return first, though, because if TLS has already failed with | |
| // the CLIENT_HELLO, then there is no need to continue toward the origin | |
| // with the blind tunnel. | |
| if (ret != EVENT_ERROR && this->handShakeReader && 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 = 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; | |
| Dbg(dbg_ctl_ssl, "Copied %" PRId64 " TLS handshake bytes to read.vio", 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; | |
| this->_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>(ink_get_hrtime() - this->get_tls_handshake_begin_time()) / 1000000000); | |
| Dbg(dbg_ctl_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) { | |
| Dbg(dbg_ctl_ssl, "ssl handshake for vc %p, expired, release the connection", this); | |
| read.triggered = 0; | |
| nh->read_ready_list.remove(this); | |
| this->_readSignalError(nh, ETIMEDOUT); | |
| return; | |
| } | |
| } | |
| // move over to the socket if we haven't already | |
| if (this->handShakeBuffer != nullptr) { | |
| bool const in_client_hello = | |
| this->get_handshake_hook_state() == TLSEventSupport::SSLHandshakeHookState::HANDSHAKE_HOOKS_CLIENT_HELLO; | |
| // Only transfer buffers to the socket once the CLIENT_HELLO is | |
| // finished. We need to keep our buffers updated until then in case we | |
| // enter tunnel mode. | |
| Dbg(dbg_ctl_ssl, "Updating our buffers, in CLIENT_HELLO: %s", in_client_hello ? "true" : "false"); | |
| read.triggered = update_rbio(!in_client_hello); | |
| } 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) { | |
| Dbg(dbg_ctl_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(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; | |
| ATS_PROBE4(net_ssl_read, this->get_fd(), bytes, s->vio.ndone, ret); | |
| if (bytes > 0) { | |
| if (ret == SSL_READ_WOULD_BLOCK || ret == SSL_READ_READY) { | |
| if (readSignalAndUpdate(VC_EVENT_READ_READY) != EVENT_CONT) { | |
| Dbg(dbg_ctl_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()) { | |
| Dbg(dbg_ctl_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); | |
| Dbg(dbg_ctl_ssl, "read finished - would block"); | |
| 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) { | |
| Dbg(dbg_ctl_ssl, "read finished - EOS"); | |
| } else { | |
| Dbg(dbg_ctl_ssl, "read finished - 0 useful bytes read, bytes used by SSL layer"); | |
| } | |
| break; | |
| case SSL_READ_COMPLETE: | |
| readSignalDone(VC_EVENT_READ_COMPLETE, nh); | |
| Dbg(dbg_ctl_ssl, "read finished - signal done"); | |
| break; | |
| case SSL_READ_ERROR: | |
| this->read.triggered = 0; | |
| this->_readSignalError(nh, (ssl_read_errno) ? ssl_read_errno : -ENET_SSL_FAILED); | |
| Dbg(dbg_ctl_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 = ink_get_hrtime(); | |
| 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; | |
| } | |
| Dbg(dbg_ctl_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); | |
| } | |
| Dbg(dbg_ctl_ssl, "towrite=%" PRId64, towrite); | |
| // SSL_write retries (WANT_WRITE/READ) must reuse the same address; thread_local gives the | |
| // coalesce path a stable one. SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER is off. | |
| static thread_local char gather_buf[SSL_MAX_TLS_RECORD_SIZE]; | |
| // Caller bounds towrite by read_avail(); asserting lets the loop skip an O(n) chain walk. | |
| ink_assert(towrite <= buf.reader()->read_avail()); | |
| ERR_clear_error(); | |
| do { | |
| IOBufferReader *reader = buf.reader(); | |
| l = towrite - total_written; | |
| // 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; | |
| Metrics::Counter::increment(ssl_rsb.total_dyn_def_tls_record_count); | |
| } else { | |
| dynamic_tls_record_size = SSL_MAX_TLS_RECORD_SIZE; | |
| Metrics::Counter::increment(ssl_rsb.total_dyn_max_tls_record_count); | |
| } | |
| if (l > dynamic_tls_record_size) { | |
| l = dynamic_tls_record_size; | |
| } | |
| } | |
| } | |
| if (!l) { | |
| break; | |
| } | |
| // Coalesce across blocks only when it fits one record; else write in place, capped to the block. | |
| const char *write_block; | |
| int64_t block_avail = reader->block_read_avail(); | |
| if (block_avail < l && l <= static_cast<int64_t>(sizeof(gather_buf))) { | |
| reader->memcpy(gather_buf, l, 0); | |
| write_block = gather_buf; | |
| } else { | |
| if (l > block_avail) { | |
| l = block_avail; | |
| } | |
| write_block = reader->start(); | |
| } | |
| try_to_write = l; | |
| num_really_written = 0; | |
| Dbg(dbg_ctl_v_ssl, "b=%p l=%" PRId64, write_block, l); | |
| err = this->_ssl_write_buffer(write_block, l, num_really_written); | |
| // We wrote all that we thought we should | |
| if (num_really_written > 0) { | |
| total_written += num_really_written; | |
| reader->consume(num_really_written); | |
| } | |
| Dbg(dbg_ctl_ssl, "try_to_write=%" PRId64 " written=%" PRId64 " total_written=%" PRId64, try_to_write, num_really_written, | |
| total_written); | |
| Metrics::Counter::increment(net_rsb.calls_to_write); | |
| } 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: | |
| Dbg(dbg_ctl_ssl, "SSL_write-SSL_ERROR_NONE"); | |
| break; | |
| case SSL_ERROR_WANT_READ: | |
| needs |= EVENTIO_READ; | |
| num_really_written = -EAGAIN; | |
| Dbg(dbg_ctl_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; | |
| Dbg(dbg_ctl_ssl_error, "SSL_write-SSL_ERROR_WANT_WRITE"); | |
| break; | |
| } | |
| case SSL_ERROR_SYSCALL: | |
| // SSL_ERROR_SYSCALL is an IO error. errno is likely 0, so set EPIPE, as | |
| // we do with SSL_ERROR_SSL below, to indicate a connection error. | |
| num_really_written = -EPIPE; | |
| Metrics::Counter::increment(ssl_rsb.error_syscall); | |
| Dbg(dbg_ctl_ssl_error, "SSL_write-SSL_ERROR_SYSCALL"); | |
| break; | |
| // end of stream | |
| case SSL_ERROR_ZERO_RETURN: | |
| num_really_written = -errno; | |
| Dbg(dbg_ctl_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; | |
| SSLVCDebug(this, "SSL_write-SSL_ERROR_SSL errno=%d", errno); | |
| Metrics::Counter::increment(ssl_rsb.error_ssl); | |
| } break; | |
| } | |
| } | |
| ATS_PROBE5(net_ssl_write, this->get_fd(), total_written, write.vio.ndone + total_written, num_really_written, | |
| static_cast<int>(err)); | |
| return num_really_written; | |
| } | |
| SSLNetVConnection::SSLNetVConnection() | |
| { | |
| this->_set_service(static_cast<ALPNSupport *>(this)); | |
| this->_set_service(static_cast<TLSBasicSupport *>(this)); | |
| this->_set_service(static_cast<TLSEventSupport *>(this)); | |
| this->_set_service(static_cast<TLSCertSwitchSupport *>(this)); | |
| this->_set_service(static_cast<TLSEarlyDataSupport *>(this)); | |
| this->_set_service(static_cast<TLSSNISupport *>(this)); | |
| this->_set_service(static_cast<TLSSessionResumptionSupport *>(this)); | |
| this->_set_service(static_cast<TLSTunnelSupport *>(this)); | |
| } | |
| void | |
| SSLNetVConnection::do_io_close(int lerrno) | |
| { | |
| // Stop any async-handshake eventfd before the VC is closed. | |
| // If WANT_ASYNC was registered the eventfd is wired into the poller with | |
| // `this` as the EventIO target. Without this stop() the SSLNetVConnection | |
| // can be freed while the eventfd still has a live epoll registration; when | |
| // the OpenSSL async job completes the poller wakes on freed memory. | |
| if (async_ep.fd >= 0) { | |
| async_ep.stop(); | |
| } | |
| 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); | |
| Dbg(dbg_ctl_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 | |
| Dbg(dbg_ctl_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 = this->con.sock.recv(&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); | |
| Dbg(dbg_ctl_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); | |
| Dbg(dbg_ctl_ssl_shutdown, "Enable quiet shutdown"); | |
| } | |
| } | |
| } | |
| // Go on and do the unix socket cleanups | |
| super::do_io_close(lerrno); | |
| } | |
| void | |
| SSLNetVConnection::do_io_shutdown(ShutdownHowTo_t howto) | |
| { | |
| if (ssl == nullptr || get_tunnel_type() == SNIRoutingType::BLIND) { | |
| // we don't have TLS layer control of blind tunnel | |
| UnixNetVConnection::do_io_shutdown(howto); | |
| return; | |
| } | |
| switch (howto) { | |
| case IO_SHUTDOWN_READ: | |
| // No need to call SSL API | |
| // SSL_shutdown() sends the close_notify alert to the peer and it only closes the write direction. | |
| // The read direction will be closed by the peer. | |
| read.enabled = 0; | |
| read.vio.buffer.clear(); | |
| read.vio.nbytes = 0; | |
| read.vio.cont = nullptr; | |
| f.shutdown |= NetEvent::SHUTDOWN_READ; | |
| break; | |
| case IO_SHUTDOWN_WRITE: | |
| SSL_shutdown(ssl); | |
| write.enabled = 0; | |
| write.vio.buffer.clear(); | |
| write.vio.nbytes = 0; | |
| write.vio.cont = nullptr; | |
| f.shutdown |= NetEvent::SHUTDOWN_WRITE; | |
| break; | |
| case IO_SHUTDOWN_READWRITE: | |
| SSL_shutdown(ssl); | |
| read.enabled = 0; | |
| write.enabled = 0; | |
| read.vio.buffer.clear(); | |
| read.vio.nbytes = 0; | |
| write.vio.buffer.clear(); | |
| write.vio.nbytes = 0; | |
| read.vio.cont = nullptr; | |
| write.vio.cont = nullptr; | |
| f.shutdown = NetEvent::SHUTDOWN_READ | NetEvent::SHUTDOWN_WRITE; | |
| break; | |
| default: | |
| ink_assert(!"not reached"); | |
| } | |
| } | |
| void | |
| SSLNetVConnection::clear() | |
| { | |
| _ca_cert_file.reset(); | |
| _ca_cert_dir.reset(); | |
| // SSL_SESSION_free() must only be called for SSL_SESSION objects, | |
| // for which the reference count was explicitly incremented (e.g. | |
| // by calling SSL_get1_session(), see SSL_get_session(3)) or when | |
| // the SSL_SESSION object was generated outside a TLS handshake | |
| // operation, e.g. by using d2i_SSL_SESSION(3). It must not be called | |
| // on other SSL_SESSION objects, as this would cause incorrect | |
| // reference counts and therefore program failures. | |
| // Since we created the shared pointer with a custom deleter, | |
| // resetting here will decrement the ref-counter. | |
| client_sess.reset(); | |
| // Stop the async-handshake eventfd before SSL_free. The | |
| // eventfd is owned by the SSL object, so SSL_free closes it; deregistering | |
| // first keeps the EPOLL_CTL_DEL operating on a valid, owned fd. clear() runs | |
| // on every free path through free_thread(), so it is the backstop in case | |
| // do_io_close() did not already stop the eventfd. | |
| if (async_ep.fd >= 0) { | |
| async_ep.stop(); | |
| } | |
| if (ssl != nullptr) { | |
| // clear() runs from free() once per VC recycle, so this is the single chokepoint where a TLS | |
| // connection's SSL object is torn down -- count it as one connection close here. Blind-tunnel | |
| // conversions free their SSL earlier (before any data) and continue as a tunnel rather than a | |
| // close, so by here ssl is already null for them and they are not counted (they are tracked by | |
| // the tunnel metrics instead). | |
| Metrics::Counter::increment(ssl_rsb.connections_closed); | |
| SSL_free(ssl); | |
| ssl = nullptr; | |
| } | |
| #if TS_USE_QMUX | |
| // The destructor never runs (ClassAllocator<SSLNetVConnection, false>), so the | |
| // QMux connection has to be released here or it leaks on every VC recycle. | |
| // Clear the QUICSupport service slot too, or a recycled non-QMux VC would | |
| // still report a (now null) QUIC connection to get_service<QUICSupport>(). | |
| _qmux_connection.reset(); | |
| this->_set_service(static_cast<QUICSupport *>(nullptr)); | |
| #endif | |
| ALPNSupport::clear(); | |
| TLSBasicSupport::clear(); | |
| TLSEventSupport::clear(); | |
| TLSSessionResumptionSupport::clear(); | |
| TLSSNISupport::_clear(); | |
| TLSTunnelSupport::_clear(); | |
| TLSCertSwitchSupport::_clear(); | |
| sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_ONGOING; | |
| sslLastWriteTime = 0; | |
| sslTotalBytesSent = 0; | |
| sslClientRenegotiationAbort = false; | |
| hookOpRequested = SslVConnOp::SSL_HOOK_OP_DEFAULT; | |
| free_handshake_buffers(); | |
| super::clear(); | |
| } | |
| void | |
| SSLNetVConnection::free_thread(EThread *t) | |
| { | |
| ink_release_assert(t == this_ethread()); | |
| // close socket fd | |
| if (con.sock.is_ok()) { | |
| release_inbound_connection_tracking(); | |
| Metrics::Gauge::decrement(net_rsb.connections_currently_open); | |
| if (is_client_exempt()) { | |
| Metrics::Gauge::decrement(net_rsb.per_client_connections_exempt_currently_open); | |
| } | |
| } | |
| con.close(); | |
| if (is_tunnel_endpoint()) { | |
| ink_assert(get_context() != NET_VCONNECTION_UNSET); | |
| Metrics::Gauge::decrement(([&]() -> Metrics::Gauge::AtomicType * { | |
| if (get_context() == NET_VCONNECTION_IN) { | |
| switch (get_tunnel_type()) { | |
| case SNIRoutingType::BLIND: | |
| return net_rsb.tunnel_current_client_connections_tls_tunnel; | |
| case SNIRoutingType::FORWARD: | |
| return net_rsb.tunnel_current_client_connections_tls_forward; | |
| case SNIRoutingType::PARTIAL_BLIND: | |
| return net_rsb.tunnel_current_client_connections_tls_partial_blind; | |
| default: | |
| return net_rsb.tunnel_current_client_connections_tls_http; | |
| } | |
| } | |
| // NET_VCONNECTION_OUT - Never a tunnel type for out (to server) context. | |
| ink_assert(get_tunnel_type() == SNIRoutingType::NONE); | |
| return net_rsb.tunnel_current_server_connections_tls; | |
| })()); | |
| } | |
| #if TS_HAS_TLS_EARLY_DATA | |
| 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; | |
| #endif | |
| clear(); | |
| SET_CONTINUATION_HANDLER(this, &SSLNetVConnection::startEvent); | |
| ink_assert(!con.sock.is_ok()); | |
| ink_assert(t == this_ethread()); | |
| if (from_accept_thread) { | |
| sslNetVCAllocator.free(this); | |
| } else { | |
| ink_assert(!con.sock.is_ok()); | |
| THREAD_FREE(this, sslNetVCAllocator, t); | |
| } | |
| } | |
| int | |
| SSLNetVConnection::_sslStartHandShake(int event, int &err) | |
| { | |
| if (TSSystemState::is_ssl_handshaking_stopped()) { | |
| Dbg(dbg_ctl_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)) { | |
| Dbg(dbg_ctl_ssl, "Failed to get dest ip, errno = [%d]", errno); | |
| return EVENT_ERROR; | |
| } | |
| SSLCertContext *cc = lookup->find(dst); | |
| if (dbg_ctl_ssl.on()) { | |
| IpEndpoint src; | |
| ip_port_text_buffer ipb1, ipb2; | |
| int ip_len = sizeof(src); | |
| if (0 != safe_getpeername(this->get_socket(), &src.sa, &ip_len)) { | |
| DbgPrint(dbg_ctl_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)); | |
| auto default_ctx = lookup->defaultContext(); | |
| DbgPrint(dbg_ctl_ssl, "IP context is %p for [%s] -> [%s], default context %p", cc, ipb2, ipb1, default_ctx.get()); | |
| } | |
| // 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 = SSLHandshakeStatus::SSL_HANDSHAKE_DONE; | |
| SSL_free(this->ssl); | |
| this->ssl = nullptr; | |
| return EVENT_DONE; | |
| } else { | |
| hookOpRequested = SslVConnOp::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().get()); | |
| } | |
| 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 *sni = this->options.sni_servername; | |
| auto nps = sniParam->get_property_config(next_hop_property_key(sni != nullptr ? sni : "", this->get_remote_addr(), buff)); | |
| shared_SSL_CTX sharedCTX = nullptr; | |
| SSL_CTX *clientCTX = nullptr; | |
| std::string caCertPathStorage; | |
| const char *caCertPath = resolve_client_ca_cert_path(params, options.ssl_client_ca_cert_path, caCertPathStorage); | |
| // First Look to see if there are override parameters | |
| Dbg(dbg_ctl_ssl, "Checking for outbound client cert override [%p]", options.ssl_client_cert_name.get()); | |
| if (options.ssl_client_cert_name) { | |
| std::string certFilePath; | |
| std::string keyFilePath; | |
| std::string caCertFilePath; | |
| // Enable override to explicitly disable the client certificate. That is, don't fill | |
| // in any of the cert paths if the cert file name is empty or "NULL". | |
| if (*options.ssl_client_cert_name != '\0' && 0 != strcasecmp("NULL", options.ssl_client_cert_name)) { | |
| certFilePath = Layout::get()->relative_to(params->clientCertPathOnly, options.ssl_client_cert_name.get()); | |
| if (options.ssl_client_private_key_name) { | |
| keyFilePath = Layout::get()->relative_to(params->clientKeyPathOnly, options.ssl_client_private_key_name); | |
| } | |
| if (options.ssl_client_ca_cert_name) { | |
| caCertFilePath = Layout::get()->relative_to(caCertPath, options.ssl_client_ca_cert_name); | |
| } | |
| Dbg(dbg_ctl_ssl, "Using outbound client cert `%s'", options.ssl_client_cert_name.get()); | |
| } else { | |
| Dbg(dbg_ctl_ssl, "Clearing outbound client cert"); | |
| } | |
| sharedCTX = params->getCTX(certFilePath, keyFilePath, | |
| caCertFilePath.empty() ? params->clientCACertFilename : caCertFilePath.c_str(), caCertPath); | |
| } else if (options.ssl_client_ca_cert_name || options.ssl_client_ca_cert_path) { | |
| std::string caCertFilePath; | |
| if (options.ssl_client_ca_cert_name) { | |
| caCertFilePath = Layout::get()->relative_to(caCertPath, options.ssl_client_ca_cert_name); | |
| } | |
| sharedCTX = params->getCTX(params->clientCertPath, params->clientKeyPath, | |
| caCertFilePath.empty() ? params->clientCACertFilename : caCertFilePath.c_str(), caCertPath); | |
| } 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(); | |
| } | |
| options.verifyServerPolicy = resolve_verify_server_policy(options.verifyServerPolicy, nps, params); | |
| if (options.verifyServerProperties != YamlSNIConfig::Property::UNSET) { | |
| // Stay with conf-override version as the highest priority | |
| } else if (nps && nps->verify_server_properties != YamlSNIConfig::Property::UNSET) { | |
| options.verifyServerProperties = nps->verify_server_properties; | |
| } 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; | |
| } | |
| // If it is negative, we are consciously not setting ALPN (e.g. for private server sessions) | |
| if (options.alpn_protocols_array_size >= 0) { | |
| if (options.alpn_protocols_array_size > 0) { | |
| SSL_set_alpn_protos(this->ssl, options.alpn_protocols_array, options.alpn_protocols_array_size); | |
| } else if (params->alpn_protocols_array_size > 0) { | |
| // Set the ALPN protocols we are requesting. | |
| SSL_set_alpn_protos(this->ssl, params->alpn_protocols_array, params->alpn_protocols_array_size); | |
| } | |
| } | |
| SSL_set_verify(this->ssl, SSL_VERIFY_PEER, verify_callback); | |
| #if TS_USE_RPK | |
| // Offer and/or pin RFC 7250 raw public keys when this next hop is configured for them. | |
| // Both are advertised alongside X.509, so a next hop that doesn't (yet) support RPK -- a | |
| // normal state during a rolling upgrade -- negotiates down to a certificate exchange. | |
| if (nps && (nps->client_rpk_enabled || nps->server_rpk_ca)) { | |
| if (!ssl_client_setup_rpk(this->ssl, nps->client_rpk_enabled, nps->server_rpk_ca, nps->server_rpk_ca_digest)) { | |
| SSLErrorVC(this, "failed to configure raw public keys for the outbound connection"); | |
| return EVENT_ERROR; | |
| } | |
| } | |
| #endif | |
| // SNI | |
| ats_scoped_str &tlsext_host_name = this->options.sni_hostname ? this->options.sni_hostname : this->options.sni_servername; | |
| if (tlsext_host_name) { | |
| if (this->set_sni_server_name(this->ssl, tlsext_host_name)) { | |
| Dbg(dbg_ctl_ssl, "using SNI name '%s' for client handshake", tlsext_host_name.get()); | |
| } else { | |
| Dbg(dbg_ctl_ssl_error, "failed to set SNI name '%s' for client handshake", tlsext_host_name.get()); | |
| Metrics::Counter::increment(ssl_rsb.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) { | |
| Dbg(dbg_ctl_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 (this->is_invoked_state()) { | |
| return SSL_WAIT_FOR_HOOK; | |
| } | |
| // Go do the preaccept hooks | |
| if (this->get_handshake_hook_state() == TLSEventSupport::SSLHandshakeHookState::HANDSHAKE_HOOKS_PRE) { | |
| if (this->invoke_tls_event() == 1) { | |
| 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 (SslVConnOp::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; | |
| } | |
| Dbg(dbg_ctl_ssl, "Go on with the handshake state=%s", | |
| TLSEventSupport::get_ssl_handshake_hook_state_name(this->get_handshake_hook_state())); | |
| // All the pre-accept hooks have completed, proceed with the actual accept. | |
| bool const in_client_hello = | |
| this->get_handshake_hook_state() == TLSEventSupport::SSLHandshakeHookState::HANDSHAKE_HOOKS_CLIENT_HELLO; | |
| // We only feed CLIENT_HELLO bytes into our temporary buffers. If we are past | |
| // the CLIENT_HELLO, then no need to buffer. A delayed ClientHello hook resumes | |
| // with the TLS library already holding the whole CLIENT_HELLO. | |
| if (in_client_hello && !this->resuming_client_hello_hooks() && this->handShakeReader) { | |
| if (BIO_ctrl_pending(SSL_get_rbio(this->ssl)) == 0) { // No more data in the buffer | |
| // Is this the first read? | |
| #if TS_USE_TLS_ASYNC | |
| if (SSLConfigParams::async_handshake_enabled) { | |
| SSL_set_mode(ssl, SSL_MODE_ASYNC); | |
| } | |
| #endif | |
| Dbg(dbg_ctl_ssl, "%p reading off the socket into our buffers", 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; | |
| } | |
| } // Still data in the BIO. Let OpenSSL consume that first before doing anything else. | |
| } | |
| 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), get_NetHandler(this->thread), EVENTIO_READ); | |
| } | |
| } | |
| } | |
| } 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); | |
| char *buf = handShakeBuffer ? handShakeBuffer->buf() : nullptr; | |
| if (buf && *buf != SSL_OP_HANDSHAKE) { | |
| SSLVCDebug(this, "SSL hanshake error with bad HS buffer"); | |
| if (getAllowPlain()) { | |
| SSLVCDebug(this, "Try plain"); | |
| // If this doesn't look like a ClientHello, convert this connection to a UnixNetVC and send the | |
| // packet for Http Processing | |
| this->_migrateFromSSL(); | |
| return SSL_RESTART; | |
| } else if (getTransparentPassThrough()) { | |
| // start a blind tunnel if tr-pass is set and data does not look like ClientHello | |
| SSLVCDebug(this, "Data does not look like SSL handshake, starting blind tunnel"); | |
| this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; | |
| sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_ONGOING; | |
| return EVENT_CONT; | |
| } else { | |
| SSLVCDebug(this, "Give up"); | |
| } | |
| } | |
| } | |
| switch (ssl_error) { | |
| case SSL_ERROR_NONE: | |
| // A resumed session carries forward the peer cert (or lack of one) from whichever connection | |
| // originally established it, and a TLS library does not ask the peer for a certificate again on | |
| // resumption. The session id context that bind_session_id_context_to_verify_scope() establishes | |
| // from the ClientHello callback binds every session to the verify_client scope it was issued | |
| // under, so the library only resumes sessions whose client certificate this SNI already | |
| // verified. Sessions issued before that binding existed, or by a build without it, carry a | |
| // different context and fall back to a full handshake. This check is the last line of defense | |
| // should a session without a certificate be resumed under a policy that requires one, which a | |
| // build with neither ClientHello callback can still admit because its earliest hook runs after | |
| // the session lookup. | |
| if (SSL_session_reused(ssl)) { | |
| int verify_mode = SSL_get_verify_mode(ssl); | |
| // A raw public key is a client's credential as much as a certificate is, and both TLS | |
| // libraries restore it with the session. | |
| bool peer_credential = this->peer_provided_cert(); | |
| #if TS_USE_RPK | |
| peer_credential = peer_credential || SSL_get0_peer_rpk(ssl) != nullptr; | |
| #endif | |
| if ((verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) != 0 && !peer_credential) { | |
| static LogMessage log_msg{IS_THROTTLED}; | |
| Metrics::Counter::increment(ssl_rsb.user_agent_resumed_cert_rejected); | |
| log_msg.warning(MakeSourceLocation(), "rejecting a resumed TLS session that does not satisfy verify_client"); | |
| SSLVCDebug(this, "rejecting a resumed TLS session that does not satisfy verify_client"); | |
| return EVENT_ERROR; | |
| } | |
| } | |
| // setClientCertCACerts() runs again from the certificate callback, after the session id context | |
| // was established, so a store that loaded for the ClientHello callback and then failed there | |
| // would leave this connection verifying against the SSL_CTX's store while the session it issues | |
| // claims the scope of the store that did load. That window is checked here rather than only in | |
| // bind_session_id_context_to_verify_scope(), which cannot see a failure that happens after it. | |
| if (this->verify_scope_digest_failed()) { | |
| static LogMessage scope_log_msg{IS_THROTTLED}; | |
| scope_log_msg.warning(MakeSourceLocation(), "rejecting a TLS session whose verify_client scope could not be established"); | |
| SSLVCDebug(this, "rejecting a TLS session whose verify_client scope could not be established"); | |
| return EVENT_ERROR; | |
| } | |
| if (dbg_ctl_ssl.on()) { | |
| #ifdef OPENSSL_IS_AT_LEAST_OPENSSL3 | |
| X509 *cert = SSL_get1_peer_certificate(ssl); | |
| #else | |
| X509 *cert = SSL_get_peer_certificate(ssl); | |
| #endif | |
| DbgPrint(dbg_ctl_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 = SSLHandshakeStatus::SSL_HANDSHAKE_DONE; | |
| if (this->get_tls_handshake_begin_time()) { | |
| this->_record_tls_handshake_end_time(); | |
| this->_update_end_of_handshake_stats(); | |
| } | |
| if (this->get_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 (this->get_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)}); | |
| Dbg(dbg_ctl_ssl, "Origin selected next protocol '%.*s'", len, proto); | |
| #if TS_USE_QMUX | |
| if (this->get_negotiated_protocol_id() == TS_ALPN_PROTOCOL_INDEX_H3QX) { | |
| Dbg(dbg_ctl_ssl, "ALPN h3qx-01: creating QMuxConnection"); | |
| _qmux_connection = std::make_unique<QMuxConnection>(this); | |
| if (_qmux_connection->is_closed()) { | |
| // Config or quiche_accept() failed. Don't advertise a QUIC connection that can | |
| // never make progress -- fail the connection instead of completing the handshake. | |
| _qmux_connection.reset(); | |
| return EVENT_ERROR; | |
| } | |
| this->_set_service(static_cast<QUICSupport *>(this)); | |
| } | |
| #endif | |
| } else { | |
| Dbg(dbg_ctl_ssl, "Origin 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 | |
| #ifdef SSL_ERROR_PENDING_CERTIFICATE | |
| case SSL_ERROR_PENDING_CERTIFICATE: | |
| #endif | |
| #if defined(SSL_ERROR_WANT_SNI_RESOLVE) || defined(SSL_ERROR_WANT_X509_LOOKUP) || defined(SSL_ERROR_PENDING_CERTIFICATE) | |
| if (this->attributes == HttpProxyPort::TRANSPORT_BLIND_TUNNEL || SslVConnOp::SSL_HOOK_OP_TUNNEL == hookOpRequested) { | |
| this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; | |
| sslHandshakeStatus = 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: | |
| Metrics::Counter::increment(ssl_rsb.error_async); | |
| return SSL_WAIT_FOR_ASYNC; | |
| #endif | |
| case SSL_ERROR_WANT_ACCEPT: | |
| return EVENT_CONT; | |
| case SSL_ERROR_SSL: { | |
| SSLVCDebug(this, "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 (this->get_handshake_hook_state() == TLSEventSupport::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.get_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; | |
| } | |
| } | |
| } | |
| } | |
| this->set_handshake_hook_state(TLSEventSupport::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 (this->is_invoked_state()) { | |
| return SSL_WAIT_FOR_HOOK; | |
| } | |
| // Go do the preaccept hooks | |
| if (this->get_handshake_hook_state() == TLSEventSupport::SSLHandshakeHookState::HANDSHAKE_HOOKS_OUTBOUND_PRE) { | |
| if (this->invoke_tls_event() == 1) { | |
| return SSL_WAIT_FOR_HOOK; | |
| } | |
| } | |
| ssl_error = this->_ssl_connect(); | |
| switch (ssl_error) { | |
| case SSL_ERROR_NONE: | |
| if (dbg_ctl_ssl.on()) { | |
| #ifdef OPENSSL_IS_AT_LEAST_OPENSSL3 | |
| X509 *cert = SSL_get1_peer_certificate(ssl); | |
| #else | |
| X509 *cert = SSL_get_peer_certificate(ssl); | |
| #endif | |
| DbgPrint(dbg_ctl_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); | |
| } | |
| } | |
| { | |
| unsigned char const *proto = nullptr; | |
| unsigned int len = 0; | |
| // Make note of the negotiated protocol | |
| SSL_get0_alpn_selected(ssl, &proto, &len); | |
| if (len == 0) { | |
| SSL_get0_next_proto_negotiated(ssl, &proto, &len); | |
| } | |
| Dbg(dbg_ctl_ssl_alpn, "Negotiated ALPN: %.*s", len, proto); | |
| this->set_negotiated_protocol_id({reinterpret_cast<const char *>(proto), static_cast<size_t>(len)}); | |
| } | |
| // if the handshake is complete and write is enabled reschedule the write | |
| if (closed == 0 && write.enabled) { | |
| writeReschedule(nh); | |
| } | |
| Metrics::Counter::increment(ssl_rsb.total_success_handshake_count_out); | |
| sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_DONE; | |
| // Record TLS handshake end time for outbound (origin) connections. | |
| // Despite the name, sslClientHandShakeEvent() handles the *outbound* side | |
| // where ATS acts as the TLS client connecting to the origin server. | |
| // (The inbound/client-facing side is handled by sslServerHandShakeEvent(), | |
| // where ATS acts as the TLS server.) | |
| // The begin time is set when the handshake starts; we record the end time | |
| // here so HttpSM can later copy both into milestones for logging. | |
| if (this->get_tls_handshake_begin_time()) { | |
| this->_record_tls_handshake_end_time(); | |
| } | |
| return EVENT_DONE; | |
| case SSL_ERROR_WANT_WRITE: | |
| Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WANT_WRITE"); | |
| return SSL_HANDSHAKE_WANT_WRITE; | |
| case SSL_ERROR_WANT_READ: | |
| Dbg(dbg_ctl_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: | |
| Dbg(dbg_ctl_ssl_error, "SSL_ERROR_WANT_CLIENT_HELLO_CB"); | |
| break; | |
| #endif | |
| case SSL_ERROR_WANT_X509_LOOKUP: | |
| Dbg(dbg_ctl_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: | |
| Dbg(dbg_ctl_ssl_error, "EOS"); | |
| return EVENT_ERROR; | |
| case SSL_ERROR_SYSCALL: | |
| err = errno; | |
| Metrics::Counter::increment(ssl_rsb.error_syscall); | |
| Dbg(dbg_ctl_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... | |
| SSLVCDebug(this, "SSL_ERROR_SSL errno=%d", errno); | |
| Metrics::Counter::increment(ssl_rsb.error_ssl); | |
| Dbg(dbg_ctl_ssl_error, "SSL_ERROR_SSL"); | |
| if (e) { | |
| if (this->options.sni_servername) { | |
| Dbg(dbg_ctl_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); | |
| Dbg(dbg_ctl_ssl_error, "SSL connection failed for '%s': %s", buff, buf); | |
| } | |
| } | |
| return EVENT_ERROR; | |
| } break; | |
| } | |
| return EVENT_CONT; | |
| } | |
| void | |
| SSLNetVConnection::reenable(int event) | |
| { | |
| Dbg(dbg_ctl_ssl, "Handshake reenable from state=%s", | |
| TLSEventSupport::get_ssl_handshake_hook_state_name(this->get_handshake_hook_state())); | |
| // Mark as error to stop the Handshake | |
| if (event == TS_EVENT_ERROR) { | |
| sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_ERROR; | |
| } | |
| // The ClientHello is only readable during the TLS library's ClientHello callback, which | |
| // the resumed handshake calls again to run the remaining ClientHello hooks. | |
| if (this->get_handshake_hook_state() == TLSEventSupport::SSLHandshakeHookState::HANDSHAKE_HOOKS_CLIENT_HELLO_INVOKE) { | |
| this->resume_tls_event(); | |
| this->readReschedule(nh); | |
| return; | |
| } | |
| this->resume_tls_event(); | |
| // The handshake callbacks of the other stages are associated with the context, which a plugin | |
| // can replace, so the library may not call them again. Walk the rest of those hooks here. | |
| if (this->invoke_tls_event() == 2) { | |
| this->write.triggered = true; | |
| this->write.enabled = true; | |
| this->writeReschedule(nh); | |
| } | |
| this->readReschedule(nh); | |
| } | |
| Continuation * | |
| SSLNetVConnection::getContinuationForTLSEvents() | |
| { | |
| return this; | |
| } | |
| EThread * | |
| SSLNetVConnection::getThreadForTLSEvents() | |
| { | |
| return this->thread; | |
| } | |
| Ptr<ProxyMutex> | |
| SSLNetVConnection::getMutexForTLSEvents() | |
| { | |
| return this->nh->mutex; | |
| } | |
| int | |
| SSLNetVConnection::populate(Connection &con, Continuation *c, void *arg) | |
| { | |
| int retval = super::populate(con, c, arg); | |
| if (retval != EVENT_DONE) { | |
| // arg is the migrated SSL this VC would adopt below; release it since we won't. | |
| if (arg != nullptr) { | |
| SSL_free(static_cast<SSL *>(arg)); | |
| } | |
| return retval; | |
| } | |
| // Add in the SSL data | |
| this->ssl = static_cast<SSL *>(arg); | |
| // Maybe bring over the stats? | |
| sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_DONE; | |
| this->_bindSSLObject(); | |
| return EVENT_DONE; | |
| } | |
| void | |
| SSLNetVConnection::_in_context_tunnel() | |
| { | |
| ink_assert(get_context() == NET_VCONNECTION_IN); | |
| Metrics::Counter::AtomicType *t; | |
| Metrics::Gauge::AtomicType *c; | |
| switch (get_tunnel_type()) { | |
| case SNIRoutingType::BLIND: | |
| t = net_rsb.tunnel_total_client_connections_tls_tunnel; | |
| c = net_rsb.tunnel_current_client_connections_tls_tunnel; | |
| break; | |
| case SNIRoutingType::FORWARD: | |
| t = net_rsb.tunnel_total_client_connections_tls_forward; | |
| c = net_rsb.tunnel_current_client_connections_tls_forward; | |
| break; | |
| case SNIRoutingType::PARTIAL_BLIND: | |
| t = net_rsb.tunnel_total_client_connections_tls_partial_blind; | |
| c = net_rsb.tunnel_current_client_connections_tls_partial_blind; | |
| break; | |
| default: | |
| t = net_rsb.tunnel_total_client_connections_tls_http; | |
| c = net_rsb.tunnel_current_client_connections_tls_http; | |
| break; | |
| } | |
| Metrics::Counter::increment(t); | |
| Metrics::Gauge::increment(c); | |
| } | |
| void | |
| SSLNetVConnection::_out_context_tunnel() | |
| { | |
| ink_assert(get_context() == NET_VCONNECTION_OUT); | |
| // Never a tunnel type for out (to server) context. | |
| ink_assert(get_tunnel_type() == SNIRoutingType::NONE); | |
| Metrics::Counter::increment(net_rsb.tunnel_total_server_connections_tls); | |
| Metrics::Gauge::increment(net_rsb.tunnel_current_server_connections_tls); | |
| } | |
| void | |
| SSLNetVConnection::increment_ssl_version_metric(int version) const | |
| { | |
| switch (version) { | |
| case SSL3_VERSION: | |
| Metrics::Counter::increment(ssl_rsb.total_sslv3); | |
| break; | |
| case TLS1_VERSION: | |
| Metrics::Counter::increment(ssl_rsb.total_tlsv1); | |
| break; | |
| case TLS1_1_VERSION: | |
| Metrics::Counter::increment(ssl_rsb.total_tlsv11); | |
| break; | |
| case TLS1_2_VERSION: | |
| Metrics::Counter::increment(ssl_rsb.total_tlsv12); | |
| break; | |
| #ifdef TLS1_3_VERSION | |
| case TLS1_3_VERSION: | |
| Metrics::Counter::increment(ssl_rsb.total_tlsv13); | |
| break; | |
| #endif | |
| default: | |
| Dbg(dbg_ctl_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; | |
| } | |
| bool | |
| SSLNetVConnection::_trackFirstHandshake() | |
| { | |
| bool retval = this->get_tls_handshake_begin_time() == 0; | |
| if (retval) { | |
| this->_record_tls_handshake_begin_time(); | |
| } | |
| return retval; | |
| } | |
| bool | |
| SSLNetVConnection::_isReadyToTransferData() const | |
| { | |
| return getSSLHandShakeComplete(); | |
| } | |
| void | |
| SSLNetVConnection::_beReadyToTransferData() | |
| { | |
| if (this->_trackFirstHandshake()) { | |
| // Eat the first write-ready. Until the TLS handshake is complete, | |
| // we should still be under the connect timeout and shouldn't bother | |
| // the state machine until the TLS handshake is complete | |
| this->write.triggered = 0; | |
| nh->write_ready_list.remove(this); | |
| } | |
| int err{0}, ret{0}; | |
| if (this->get_context() == NET_VCONNECTION_OUT) { | |
| ret = this->_sslStartHandShake(SSL_EVENT_CLIENT, err); | |
| } else { | |
| ret = this->_sslStartHandShake(SSL_EVENT_SERVER, err); | |
| } | |
| if (ret == EVENT_ERROR) { | |
| this->write.triggered = 0; | |
| this->_writeSignalError(nh, err); | |
| } else if (ret == SSL_HANDSHAKE_WANT_READ || ret == SSL_HANDSHAKE_WANT_ACCEPT) { | |
| this->read.triggered = 0; | |
| nh->read_ready_list.remove(this); | |
| this->readReschedule(nh); | |
| } else if (ret == SSL_HANDSHAKE_WANT_CONNECT || ret == SSL_HANDSHAKE_WANT_WRITE) { | |
| this->write.triggered = 0; | |
| nh->write_ready_list.remove(this); | |
| this->writeReschedule(nh); | |
| } else if (ret == EVENT_DONE) { | |
| this->write.triggered = 1; | |
| if (this->write.enabled) { | |
| nh->write_ready_list.in_or_enqueue(this); | |
| } | |
| // If this was driven by a zero length read, signal complete when | |
| // the handshake is complete. Otherwise set up for continuing read | |
| // operations. | |
| if (this->write.vio.ntodo() <= 0) { | |
| this->readSignalDone(VC_EVENT_WRITE_COMPLETE, nh); | |
| } | |
| } else if (ret == SSL_WAIT_FOR_HOOK && this->is_invoked_state()) { | |
| // A plugin holds the handshake, and reenable() resumes it. Rescheduling | |
| // here can put this VC straight back on the write ready list being | |
| // drained, spinning the thread. | |
| } else { | |
| this->writeReschedule(nh); | |
| } | |
| } | |
| in_port_t | |
| SSLNetVConnection::_get_local_port() | |
| { | |
| return this->get_local_port(); | |
| } | |
| bool | |
| SSLNetVConnection::_isTryingRenegotiation() const | |
| { | |
| if (SSLConfigParams::ssl_allow_client_renegotiation == false && this->getSSLHandShakeComplete()) { | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| shared_SSL_CTX | |
| SSLNetVConnection::_lookupContextByName(const std::string &servername, SSLCertContextType ctxType) | |
| { | |
| shared_SSL_CTX ctx = nullptr; | |
| SSLCertificateConfig::scoped_config lookup; | |
| SSLCertContext *cc = lookup->find(servername, ctxType); | |
| if (cc) { | |
| ctx = cc->getCtx(); | |
| } | |
| if (cc && ctx && SSLCertContextOption::OPT_TUNNEL == cc->opt && this->get_is_transparent()) { | |
| this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; | |
| this->setSSLHandShakeComplete(SSLHandshakeStatus::SSL_HANDSHAKE_DONE); | |
| return nullptr; | |
| } else { | |
| return ctx; | |
| } | |
| } | |
| shared_SSL_CTX | |
| SSLNetVConnection::_lookupContextByIP() | |
| { | |
| shared_SSL_CTX ctx = nullptr; | |
| SSLCertificateConfig::scoped_config lookup; | |
| IpEndpoint ip; | |
| int namelen = sizeof(ip); | |
| // Return null if this vc is already configured as a tunnel | |
| if (this->attributes == HttpProxyPort::TRANSPORT_BLIND_TUNNEL) { | |
| return nullptr; | |
| } | |
| SSLCertContext *cc = nullptr; | |
| if (IpEndpoint const *proxy_protocol_dst = this->_proxy_protocol_dst_endpoint(); proxy_protocol_dst != nullptr) { | |
| ip = *proxy_protocol_dst; | |
| ip_port_text_buffer ipb1; | |
| ats_ip_nptop(&ip, ipb1, sizeof(ipb1)); | |
| cc = lookup->find(ip); | |
| if (dbg_ctl_proxyprotocol.on()) { | |
| IpEndpoint src; | |
| ip_port_text_buffer ipb2; | |
| int ip_len = sizeof(src); | |
| if (0 != safe_getpeername(this->get_socket(), &src.sa, &ip_len)) { | |
| DbgPrint(dbg_ctl_proxyprotocol, "Failed to get src ip, errno = [%d]", errno); | |
| return nullptr; | |
| } | |
| ats_ip_nptop(&src, ipb2, sizeof(ipb2)); | |
| auto default_ctx = lookup->defaultContext(); | |
| DbgPrint(dbg_ctl_proxyprotocol, "IP context is %p for [%s] -> [%s], default context %p", cc, ipb2, ipb1, default_ctx.get()); | |
| } | |
| } else if (0 == safe_getsockname(this->get_socket(), &ip.sa, &namelen)) { | |
| cc = lookup->find(ip); | |
| } | |
| if (cc) { | |
| ctx = cc->getCtx(); | |
| } | |
| return ctx; | |
| } | |
| IpEndpoint const * | |
| SSLNetVConnection::_proxy_protocol_dst_endpoint() const | |
| { | |
| if (this->get_is_proxy_protocol() && this->get_proxy_protocol_version() != ProxyProtocolVersion::UNDEFINED && | |
| this->get_proxy_protocol_dst_addr() != nullptr) { | |
| return &pp_info.dst_addr; | |
| } | |
| return nullptr; | |
| } | |
| 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; | |
| } | |
| char * | |
| SSLNetVConnection::_getCoalescedHandShakeBuffer(int64_t total_chain_size) | |
| { | |
| if (this->coalescedHandShakeBioBuffer != nullptr) { | |
| ats_free(this->coalescedHandShakeBioBuffer); | |
| this->coalescedHandShakeBioBuffer = nullptr; | |
| } | |
| char *start = this->handShakeReader->start(); | |
| char *end = this->handShakeReader->end(); | |
| char *coalescedBuffer = start; | |
| if ((end - start) < total_chain_size) { | |
| this->coalescedHandShakeBioBuffer = static_cast<char *>(ats_malloc(total_chain_size)); | |
| this->handShakeReader->memcpy(this->coalescedHandShakeBioBuffer, total_chain_size); | |
| coalescedBuffer = this->coalescedHandShakeBioBuffer; | |
| } | |
| return coalescedBuffer; | |
| } | |
| void | |
| SSLNetVConnection::_propagateHandShakeBuffer(UnixNetVConnection *target, EThread *t) | |
| { | |
| Dbg(dbg_ctl_ssl, "allow-plain, handshake buffer ready to read=%" PRId64, this->handShakeHolder->read_avail()); | |
| // Take ownership of the handShake buffer | |
| this->sslHandshakeStatus = SSLHandshakeStatus::SSL_HANDSHAKE_DONE; | |
| NetState *s = &target->read; | |
| s->vio.set_writer(this->handShakeBuffer); | |
| s->vio.set_reader(this->handShakeHolder); | |
| this->handShakeHolder = nullptr; | |
| this->handShakeBuffer = nullptr; | |
| s->vio.vc_server = target; | |
| s->vio.cont = this->read.vio.cont; | |
| s->vio.mutex = this->read.vio.cont->mutex; | |
| // Passing along the buffer, don't keep a reading holding early in the buffer | |
| this->handShakeReader->dealloc(); | |
| this->handShakeReader = nullptr; | |
| if (this->coalescedHandShakeBioBuffer != nullptr) { | |
| ats_free(this->coalescedHandShakeBioBuffer); | |
| this->coalescedHandShakeBioBuffer = nullptr; | |
| } | |
| // Kick things again, so the data that was copied into the | |
| // vio.read buffer gets processed | |
| target->readSignalDone(VC_EVENT_READ_COMPLETE, get_NetHandler(t)); | |
| } | |
| /* | |
| * Replaces the current SSLNetVConnection with a UnixNetVConnection | |
| * Propagates any data in the SSL handShakeBuffer to be processed | |
| * by the UnixNetVConnection logic | |
| */ | |
| UnixNetVConnection * | |
| SSLNetVConnection::_migrateFromSSL() | |
| { | |
| EThread *t = this_ethread(); | |
| NetHandler *client_nh = get_NetHandler(t); | |
| ink_assert(client_nh); | |
| Connection hold_con; | |
| hold_con.move(this->con); | |
| // We will leave the SSL object with the original SSLNetVC to be | |
| // cleaned up. Only moving the socket and handShakeBuffer | |
| // So no need to call _prepareMigration | |
| // Do_io_close will signal the VC to be freed on the original thread | |
| // Since we moved the con context, the fd will not be closed | |
| // Go ahead and remove the fd from the original thread's epoll structure, so it is not | |
| // processed on two threads simultaneously | |
| this->ep.stop(); | |
| // Create new VC: | |
| UnixNetVConnection *newvc = static_cast<UnixNetVConnection *>(unix_netProcessor.allocate_vc(t)); | |
| ink_assert(newvc != nullptr); | |
| if (newvc != nullptr) { | |
| // Before populate() gives the socket to the new VC, which updates the exempt gauge when it is freed. | |
| newvc->set_client_exempt(is_client_exempt()); | |
| } | |
| if (newvc != nullptr && newvc->populate(hold_con, this->read.vio.cont, nullptr) != EVENT_DONE) { | |
| newvc->do_io_close(); | |
| Dbg(dbg_ctl_ssl, "Failed to populate unixvc for allow-plain"); | |
| newvc = nullptr; | |
| } | |
| if (newvc != nullptr) { | |
| newvc->attributes = HttpProxyPort::TRANSPORT_DEFAULT; | |
| newvc->set_is_transparent(this->is_transparent); | |
| newvc->set_context(get_context()); | |
| newvc->options = this->options; | |
| Dbg(dbg_ctl_ssl, "Move to unixvc for allow-plain"); | |
| this->_propagateHandShakeBuffer(newvc, t); | |
| } | |
| // Do not mark this closed until the end so it does not get freed by the other thread too soon | |
| this->do_io_close(); | |
| return newvc; | |
| } | |
| ssl_curve_id | |
| SSLNetVConnection::_get_tls_curve() const | |
| { | |
| // For resumed server side session caching, we have to retrieve the curve/group | |
| // from our stored data. For non-resumed sessions or from ticket based resumption, | |
| // simply query the SSL object. | |
| if (getIsResumedFromSessionCache()) { | |
| return getSSLCurveNID(); | |
| } else { | |
| return SSLGetCurveNID(ssl); | |
| } | |
| } | |
| std::string_view | |
| SSLNetVConnection::_get_tls_group() const | |
| { | |
| // For resumed server side session caching, we have to retrieve the curve/group | |
| // from our stored data. For non-resumed sessions or from ticket based resumption, | |
| // simply query the SSL object. | |
| if (getIsResumedFromSessionCache()) { | |
| return getSSLGroupName(); | |
| } else { | |
| return SSLGetGroupName(ssl); | |
| } | |
| } | |
| int | |
| SSLNetVConnection::_verify_certificate(X509_STORE_CTX * /* ctx ATS_UNUSED */) | |
| { | |
| // Currently, TS_EVENT_SSL_VERIFY_CLIENT/SERVER are invoked only with a NetVC instance. | |
| // This requires plugins to call TSSslVerifyCTX in their event handler. | |
| // We could pass a structure that has both a cert to verify and a NetVC. | |
| // It would allow us to remove confusing TSSslVerifyCTX and its internal implementation that are only available during a very | |
| // limited time. | |
| if (get_context() == NET_VCONNECTION_IN) { | |
| this->callHooks(TS_EVENT_SSL_VERIFY_CLIENT /* , ctx */); | |
| } else { | |
| this->callHooks(TS_EVENT_SSL_VERIFY_SERVER /* , ctx */); | |
| } | |
| if (this->sslHandshakeStatus == SSLHandshakeStatus::SSL_HANDSHAKE_ERROR) { | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| 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 (!this->_early_data_finish) { | |
| #if HAVE_SSL_READ_EARLY_DATA | |
| size_t nread = 0; | |
| #else | |
| ssize_t nread = 0; | |
| #endif | |
| while (true) { | |
| bool had_error_on_reading_early_data = false; | |
| bool finished_reading_early_data = false; | |
| IOBufferBlock *block = new_IOBufferBlock(); | |
| block->alloc(BUFFER_SIZE_INDEX_16K); | |
| #if HAVE_SSL_READ_EARLY_DATA | |
| ret = SSL_read_early_data(ssl, block->buf(), index_to_buffer_size(BUFFER_SIZE_INDEX_16K), &nread); | |
| if (ret == SSL_READ_EARLY_DATA_ERROR) { | |
| had_error_on_reading_early_data = true; | |
| } else if (ret == SSL_READ_EARLY_DATA_FINISH) { | |
| finished_reading_early_data = true; | |
| } | |
| #else | |
| // If SSL_read_early_data is unavailable, it's probably BoringSSL, | |
| // and SSL_in_early_data should be available. | |
| ret = SSL_accept(ssl); | |
| if (ret <= 0) { | |
| had_error_on_reading_early_data = true; | |
| } else { | |
| if (SSL_in_early_data(ssl)) { | |
| ret = SSL_read(ssl, block->buf(), index_to_buffer_size(BUFFER_SIZE_INDEX_16K)); | |
| finished_reading_early_data = !SSL_in_early_data(ssl); | |
| if (ret < 0) { | |
| nread = 0; | |
| if (finished_reading_early_data) { | |
| ret = 2; // SSL_READ_EARLY_DATA_FINISH | |
| } else { | |
| // Don't override ret here. | |
| // Keeping the original retrurn value let ATS allow to check the value by SSL_get_error. | |
| // That gives a chance to progress handshake process, or shutdown a connection if the error is serious. | |
| had_error_on_reading_early_data = true; | |
| } | |
| } else { | |
| nread = ret; | |
| if (finished_reading_early_data) { | |
| ret = 2; // SSL_READ_EARLY_DATA_FINISH | |
| } else { | |
| ret = 1; // SSL_READ_EARLY_DATA_SUCCESS | |
| } | |
| } | |
| } else { | |
| nread = 0; | |
| ret = 2; // SSL_READ_EARLY_DATA_FINISH | |
| finished_reading_early_data = true; | |
| } | |
| } | |
| #endif | |
| if (had_error_on_reading_early_data) { | |
| Dbg(dbg_ctl_ssl_early_data, "Error on reading early data: %d", ret); | |
| 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); | |
| this->_increment_early_data_len(nread); | |
| Metrics::Counter::increment(ssl_rsb.early_data_received_count); | |
| if (dbg_ctl_ssl_early_data_show_received.on()) { | |
| std::string early_data_str(reinterpret_cast<char *>(block->buf()), nread); | |
| DbgPrint(dbg_ctl_ssl_early_data_show_received, "Early data buffer: \n%s", early_data_str.c_str()); | |
| } | |
| } else { | |
| block->free(); | |
| } | |
| if (finished_reading_early_data) { | |
| this->_early_data_finish = true; | |
| Dbg(dbg_ctl_ssl_early_data, "SSL_READ_EARLY_DATA_FINISH: size = %lu", nread); | |
| if (this->_early_data_reader == nullptr || this->_early_data_reader->read_avail() == 0) { | |
| Dbg(dbg_ctl_ssl_early_data, "no data in early data buffer"); | |
| ERR_clear_error(); | |
| ret = SSL_accept(ssl); | |
| } | |
| break; | |
| } | |
| Dbg(dbg_ctl_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 && dbg_ctl_ssl_error_accept.on()) { | |
| char buf[512]; | |
| unsigned long e = ERR_peek_last_error(); | |
| ERR_error_string_n(e, buf, sizeof(buf)); | |
| DbgPrint(dbg_ctl_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 (first_ssl_connect) { | |
| first_ssl_connect = false; | |
| 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; | |
| swoc::bwprint(lookup_key, "{}:{}:{}", sni_addr.c_str(), SSL_get_SSL_CTX(ssl), get_verify_str(ssl)); | |
| Dbg(dbg_ctl_ssl_origin_session_cache, "origin session cache lookup key = %s", lookup_key.c_str()); | |
| std::shared_ptr<SSL_SESSION> shared_sess = this->getOriginSession(lookup_key); | |
| if (shared_sess && SSL_set_session(ssl, shared_sess.get())) { | |
| // Keep a reference of this shared pointer in the connection | |
| this->client_sess = std::move(shared_sess); | |
| } | |
| } | |
| } | |
| } | |
| int ret = SSL_connect(ssl); | |
| if (ret > 0) { | |
| if (SSL_session_reused(ssl)) { | |
| Metrics::Counter::increment(ssl_rsb.origin_session_reused_count); | |
| Dbg(dbg_ctl_ssl_origin_session_cache, "reused session to origin server"); | |
| #if TS_USE_RPK | |
| origin_rpk_record_resumed_peer_key(ssl); | |
| #endif | |
| } else { | |
| // The origin can decline an offered session, so the resumption state and | |
| // the cached curve and group recorded at lookup do not describe this | |
| // full handshake. | |
| TLSSessionResumptionSupport::clear(); | |
| Dbg(dbg_ctl_ssl_origin_session_cache, "new session to origin server"); | |
| } | |
| return SSL_ERROR_NONE; | |
| } | |
| int ssl_error = SSL_get_error(ssl, ret); | |
| if (ssl_error == SSL_ERROR_SSL && dbg_ctl_ssl_error_connect.on()) { | |
| char buf[512]; | |
| unsigned long e = ERR_peek_last_error(); | |
| ERR_error_string_n(e, buf, sizeof(buf)); | |
| DbgPrint(dbg_ctl_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; | |
| } | |
| int ret; | |
| // If SSL_write_early_data is available, it's probably OpenSSL, | |
| // and SSL_is_init_finished should be available. | |
| // If SSL_write_early_data is unavailable, its' probably BoringSSL, | |
| // and we can use SSL_write to send early data. | |
| #if TS_HAS_TLS_EARLY_DATA | |
| if (SSL_version(ssl) >= TLS1_3_VERSION) { | |
| #ifdef HAVE_SSL_WRITE_EARLY_DATA | |
| if (SSL_is_init_finished(ssl)) { | |
| #endif | |
| ret = SSL_write(ssl, buf, static_cast<int>(nbytes)); | |
| #ifdef HAVE_SSL_WRITE_EARLY_DATA | |
| } else { | |
| size_t nwrite; | |
| ret = SSL_write_early_data(ssl, buf, static_cast<size_t>(nbytes), &nwrite); | |
| if (ret == 1) { | |
| ret = nwrite; | |
| } | |
| } | |
| #endif | |
| } 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 && dbg_ctl_ssl_error_write.on()) { | |
| char tempbuf[512]; | |
| unsigned long e = ERR_peek_last_error(); | |
| ERR_error_string_n(e, tempbuf, sizeof(tempbuf)); | |
| DbgPrint(dbg_ctl_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) { | |
| Dbg(dbg_ctl_ssl_early_data, "Reading from early data buffer."); | |
| this->_increment_early_data_len(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; | |
| } | |
| bool early_data_enabled = this->hints_from_sni.server_max_early_data.has_value() ? | |
| this->hints_from_sni.server_max_early_data.value() > 0 : | |
| SSLConfigParams::server_max_early_data > 0; | |
| if (early_data_enabled && !this->_early_data_finish) { | |
| bool had_error_on_reading_early_data = false; | |
| bool finished_reading_early_data = false; | |
| Dbg(dbg_ctl_ssl_early_data, "More early data to read."); | |
| ssl_error_t ssl_error = SSL_ERROR_NONE; | |
| int ret; | |
| #if HAVE_SSL_READ_EARLY_DATA | |
| size_t read_bytes = 0; | |
| #else | |
| ssize_t read_bytes = 0; | |
| #endif | |
| #if HAVE_SSL_READ_EARLY_DATA | |
| ret = SSL_read_early_data(ssl, buf, static_cast<size_t>(nbytes), &read_bytes); | |
| if (ret == SSL_READ_EARLY_DATA_ERROR) { | |
| had_error_on_reading_early_data = true; | |
| ssl_error = SSL_get_error(ssl, ret); | |
| } else if (ret == SSL_READ_EARLY_DATA_FINISH) { | |
| finished_reading_early_data = true; | |
| } | |
| #else | |
| // If SSL_read_early_data is unavailable, it's probably OpenSSL, | |
| // and SSL_in_early_data should be available. | |
| if (SSL_in_early_data(ssl)) { | |
| ret = SSL_read(ssl, buf, nbytes); | |
| finished_reading_early_data = !SSL_in_early_data(ssl); | |
| if (ret < 0) { | |
| if (!finished_reading_early_data) { | |
| had_error_on_reading_early_data = true; | |
| ssl_error = SSL_get_error(ssl, ret); | |
| } | |
| read_bytes = 0; | |
| } else { | |
| read_bytes = ret; | |
| } | |
| } else { | |
| finished_reading_early_data = true; | |
| read_bytes = 0; | |
| } | |
| #endif | |
| if (had_error_on_reading_early_data) { | |
| Dbg(dbg_ctl_ssl_early_data, "Error reading early data: %s", ERR_error_string(ERR_get_error(), nullptr)); | |
| } else { | |
| if ((nread = read_bytes) > 0) { | |
| this->_increment_early_data_len(read_bytes); | |
| Metrics::Counter::increment(ssl_rsb.early_data_received_count); | |
| if (dbg_ctl_ssl_early_data_show_received.on()) { | |
| std::string early_data_str(reinterpret_cast<char *>(buf), nread); | |
| DbgPrint(dbg_ctl_ssl_early_data_show_received, "Early data buffer: \n%s", early_data_str.c_str()); | |
| } | |
| } | |
| if (finished_reading_early_data) { | |
| this->_early_data_finish = true; | |
| Dbg(dbg_ctl_ssl_early_data, "SSL_READ_EARLY_DATA_FINISH: size = %" PRId64, nread); | |
| } else { | |
| Dbg(dbg_ctl_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 && dbg_ctl_ssl_error_read.on()) { | |
| char tempbuf[512]; | |
| unsigned long e = ERR_peek_last_error(); | |
| ERR_error_string_n(e, tempbuf, sizeof(tempbuf)); | |
| DbgPrint(dbg_ctl_ssl_error_read, "SSL read returned %d, ssl_error=%d, ERR_get_error=%ld (%s)", ret, ssl_error, e, tempbuf); | |
| } | |
| return ssl_error; | |
| } |