blob: 83ff6d67bc111d478a7850e422df8c4bfa097129 [file] [log] [blame]
/** @file
A brief file description
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/****************************************************************************
SSLNetVConnection.h
This file implements an I/O Processor for network I/O.
****************************************************************************/
#pragma once
#include <memory>
#include "tscore/ink_platform.h"
#include "ts/apidefs.h"
#include <string_view>
#include <cstring>
#include <memory>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/objects.h>
#include "P_EventSystem.h"
#include "P_UnixNetVConnection.h"
#include "P_UnixNet.h"
#include "P_ALPNSupport.h"
#include "P_SSLUtils.h"
// These are included here because older OpenSSL libraries don't have them.
// Don't copy these defines, or use their values directly, they are merely
// here to avoid compiler errors.
#ifndef SSL_TLSEXT_ERR_OK
#define SSL_TLSEXT_ERR_OK 0
#endif
#ifndef SSL_TLSEXT_ERR_NOACK
#define SSL_TLSEXT_ERR_NOACK 3
#endif
#define SSL_OP_HANDSHAKE 0x16
// TS-2503: dynamic TLS record sizing
// For smaller records, we should also reserve space for various TCP options
// (timestamps, SACKs.. up to 40 bytes [1]), and account for TLS record overhead
// (another 20-60 bytes on average, depending on the negotiated ciphersuite [2]).
// All in all: 1500 - 40 (IP) - 20 (TCP) - 40 (TCP options) - TLS overhead (60-100)
// For larger records, the size is determined by TLS protocol record size
#define SSL_DEF_TLS_RECORD_SIZE 1300 // 1500 - 40 (IP) - 20 (TCP) - 40 (TCP options) - TLS overhead (60-100)
#define SSL_MAX_TLS_RECORD_SIZE 16383 // 2^14 - 1
#define SSL_DEF_TLS_RECORD_BYTE_THRESHOLD 1000000
#define SSL_DEF_TLS_RECORD_MSEC_THRESHOLD 1000
struct SSLCertLookup;
typedef enum {
SSL_HOOK_OP_DEFAULT, ///< Null / initialization value. Do normal processing.
SSL_HOOK_OP_TUNNEL, ///< Switch to blind tunnel
SSL_HOOK_OP_TERMINATE, ///< Termination connection / transaction.
SSL_HOOK_OP_LAST = SSL_HOOK_OP_TERMINATE ///< End marker value.
} SslVConnOp;
enum SSLHandshakeStatus { SSL_HANDSHAKE_ONGOING, SSL_HANDSHAKE_DONE, SSL_HANDSHAKE_ERROR };
//////////////////////////////////////////////////////////////////
//
// class NetVConnection
//
// A VConnection for a network socket.
//
//////////////////////////////////////////////////////////////////
class SSLNetVConnection : public UnixNetVConnection, public ALPNSupport
{
typedef UnixNetVConnection super; ///< Parent type.
public:
int sslStartHandShake(int event, int &err) override;
void clear() override;
void free(EThread *t) override;
virtual void
enableRead()
{
read.enabled = 1;
write.enabled = 1;
}
bool
trackFirstHandshake() override
{
bool retval = sslHandshakeBeginTime == 0;
if (retval) {
sslHandshakeBeginTime = Thread::get_hrtime();
}
return retval;
}
bool
getSSLHandShakeComplete() const override
{
return sslHandshakeStatus != SSL_HANDSHAKE_ONGOING;
}
virtual void
setSSLHandShakeComplete(enum SSLHandshakeStatus state)
{
sslHandshakeStatus = state;
}
void
setSSLSessionCacheHit(bool state)
{
sslSessionCacheHit = state;
}
bool
getSSLSessionCacheHit() const
{
return sslSessionCacheHit;
}
int sslServerHandShakeEvent(int &err);
int sslClientHandShakeEvent(int &err);
void net_read_io(NetHandler *nh, EThread *lthread) override;
int64_t load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs) override;
void do_io_close(int lerrno = -1) override;
////////////////////////////////////////////////////////////
// Instances of NetVConnection should be allocated //
// only from the free list using NetVConnection::alloc(). //
// The constructor is public just to avoid compile errors.//
////////////////////////////////////////////////////////////
SSLNetVConnection();
~SSLNetVConnection() override {}
static int advertise_next_protocol(SSL *ssl, const unsigned char **out, unsigned *outlen, void *);
static int select_next_protocol(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in,
unsigned inlen, void *);
bool
getSSLClientRenegotiationAbort() const
{
return sslClientRenegotiationAbort;
}
void
setSSLClientRenegotiationAbort(bool state)
{
sslClientRenegotiationAbort = state;
}
bool
getTransparentPassThrough() const
{
return transparentPassThrough;
}
void
setTransparentPassThrough(bool val)
{
transparentPassThrough = val;
}
// Copy up here so we overload but don't override
using super::reenable;
/// Reenable the VC after a pre-accept or SNI hook is called.
virtual void reenable(NetHandler *nh, int event = TS_EVENT_CONTINUE);
/// Set the SSL context.
/// @note This must be called after the SSL endpoint has been created.
virtual bool sslContextSet(void *ctx);
int64_t read_raw_data();
void
initialize_handshake_buffers()
{
this->handShakeBuffer = new_MIOBuffer();
this->handShakeReader = this->handShakeBuffer->alloc_reader();
this->handShakeHolder = this->handShakeReader->clone();
this->handShakeBioStored = 0;
}
void
free_handshake_buffers()
{
if (this->handShakeReader) {
this->handShakeReader->dealloc();
}
if (this->handShakeHolder) {
this->handShakeHolder->dealloc();
}
if (this->handShakeBuffer) {
free_MIOBuffer(this->handShakeBuffer);
}
this->handShakeReader = nullptr;
this->handShakeHolder = nullptr;
this->handShakeBuffer = nullptr;
this->handShakeBioStored = 0;
}
// Returns true if all the hooks reenabled
bool callHooks(TSEvent eventId);
// Returns true if we have already called at
// least some of the hooks
bool
calledHooks(TSEvent eventId) const
{
bool retval = false;
switch (this->sslHandshakeHookState) {
case HANDSHAKE_HOOKS_PRE:
case HANDSHAKE_HOOKS_PRE_INVOKE:
if (eventId == TS_EVENT_VCONN_START) {
if (curHook) {
retval = true;
}
}
break;
case HANDSHAKE_HOOKS_CLIENT_HELLO:
case HANDSHAKE_HOOKS_CLIENT_HELLO_INVOKE:
if (eventId == TS_EVENT_VCONN_START) {
retval = true;
} else if (eventId == TS_EVENT_SSL_CLIENT_HELLO) {
if (curHook) {
retval = true;
}
}
break;
case HANDSHAKE_HOOKS_SNI:
if (eventId == TS_EVENT_VCONN_START || eventId == TS_EVENT_SSL_CLIENT_HELLO) {
retval = true;
} else if (eventId == TS_EVENT_SSL_SERVERNAME) {
if (curHook) {
retval = true;
}
}
break;
case HANDSHAKE_HOOKS_CERT:
case HANDSHAKE_HOOKS_CERT_INVOKE:
if (eventId == TS_EVENT_VCONN_START || eventId == TS_EVENT_SSL_CLIENT_HELLO || eventId == TS_EVENT_SSL_SERVERNAME) {
retval = true;
} else if (eventId == TS_EVENT_SSL_CERT) {
if (curHook) {
retval = true;
}
}
break;
case HANDSHAKE_HOOKS_CLIENT_CERT:
case HANDSHAKE_HOOKS_CLIENT_CERT_INVOKE:
if (eventId == TS_EVENT_SSL_VERIFY_CLIENT || eventId == TS_EVENT_VCONN_START) {
retval = true;
}
break;
case HANDSHAKE_HOOKS_OUTBOUND_PRE:
case HANDSHAKE_HOOKS_OUTBOUND_PRE_INVOKE:
if (eventId == TS_EVENT_VCONN_OUTBOUND_START) {
if (curHook) {
retval = true;
}
}
break;
case HANDSHAKE_HOOKS_VERIFY_SERVER:
retval = (eventId == TS_EVENT_SSL_VERIFY_SERVER);
break;
case HANDSHAKE_HOOKS_DONE:
retval = true;
break;
}
return retval;
}
const char *
getSSLProtocol() const
{
return ssl ? SSL_get_version(ssl) : nullptr;
}
const char *
getSSLCipherSuite() const
{
return ssl ? SSL_get_cipher_name(ssl) : nullptr;
}
void
setSSLCurveNID(ssl_curve_id curve_nid)
{
sslCurveNID = curve_nid;
}
ssl_curve_id
getSSLCurveNID() const
{
return sslCurveNID;
}
const char *
getSSLCurve() const
{
if (!ssl) {
return nullptr;
}
ssl_curve_id curve = getSSLCurveNID();
#ifndef OPENSSL_IS_BORINGSSL
if (curve == NID_undef) {
return nullptr;
}
return OBJ_nid2sn(curve);
#else
if (curve == 0) {
return nullptr;
}
return SSL_get_curve_name(curve);
#endif
}
bool
has_tunnel_destination() const
{
return tunnel_host != nullptr;
}
const char *
get_tunnel_host() const
{
return tunnel_host;
}
ushort
get_tunnel_port() const
{
return tunnel_port;
}
/* Returns true if this vc was configured for forward_route
*/
bool
decrypt_tunnel()
{
return has_tunnel_destination() && tunnel_decrypt;
}
void
set_tunnel_destination(const std::string_view &destination, bool decrypt)
{
auto pos = destination.find(":");
if (nullptr != tunnel_host) {
ats_free(tunnel_host);
}
if (pos != std::string::npos) {
tunnel_port = std::stoi(destination.substr(pos + 1).data());
tunnel_host = ats_strndup(destination.substr(0, pos).data(), pos);
} else {
tunnel_port = 0;
tunnel_host = ats_strndup(destination.data(), destination.length());
}
tunnel_decrypt = decrypt;
}
int populate_protocol(std::string_view *results, int n) const override;
const char *protocol_contains(std::string_view tag) const override;
/**
* Populate the current object based on the socket information in in the
* con parameter and the ssl object in the arg parameter
* This is logic is invoked when the NetVC object is created in a new thread context
*/
int populate(Connection &con, Continuation *c, void *arg) override;
SSL *ssl = nullptr;
ink_hrtime sslHandshakeBeginTime = 0;
ink_hrtime sslHandshakeEndTime = 0;
ink_hrtime sslLastWriteTime = 0;
int64_t sslTotalBytesSent = 0;
// The serverName is either a pointer to the (null-terminated) name fetched from the
// SSL object or the empty string.
const char *
get_server_name() const
{
return _serverName.get() ? _serverName.get() : "";
}
void set_server_name(std::string_view name);
/// Set by asynchronous hooks to request a specific operation.
SslVConnOp hookOpRequested = SSL_HOOK_OP_DEFAULT;
// noncopyable
SSLNetVConnection(const SSLNetVConnection &) = delete;
SSLNetVConnection &operator=(const SSLNetVConnection &) = delete;
bool protocol_mask_set = false;
unsigned long protocol_mask;
// early data related stuff
bool early_data_finish = false;
MIOBuffer *early_data_buf = nullptr;
IOBufferReader *early_data_reader = nullptr;
int64_t read_from_early_data = 0;
// Only applies during the VERIFY certificate hooks (client and server side)
// Means to give the plugin access to the data structure passed in during the underlying
// openssl callback so the plugin can make more detailed decisions about the
// validity of the certificate in their cases
X509_STORE_CTX *
get_verify_cert()
{
return verify_cert;
}
void
set_verify_cert(X509_STORE_CTX *ctx)
{
verify_cert = ctx;
}
private:
std::string_view map_tls_protocol_to_tag(const char *proto_string) const;
bool update_rbio(bool move_to_socket);
void increment_ssl_version_metric(int version) const;
void fetch_ssl_curve();
enum SSLHandshakeStatus sslHandshakeStatus = SSL_HANDSHAKE_ONGOING;
bool sslClientRenegotiationAbort = false;
bool sslSessionCacheHit = false;
MIOBuffer *handShakeBuffer = nullptr;
IOBufferReader *handShakeHolder = nullptr;
IOBufferReader *handShakeReader = nullptr;
int handShakeBioStored = 0;
int sslCurveNID = NID_undef;
bool transparentPassThrough = false;
/// The current hook.
/// @note For @C SSL_HOOKS_INVOKE, this is the hook to invoke.
class APIHook *curHook = nullptr;
enum SSLHandshakeHookState {
HANDSHAKE_HOOKS_PRE,
HANDSHAKE_HOOKS_PRE_INVOKE,
HANDSHAKE_HOOKS_CLIENT_HELLO,
HANDSHAKE_HOOKS_CLIENT_HELLO_INVOKE,
HANDSHAKE_HOOKS_SNI,
HANDSHAKE_HOOKS_CERT,
HANDSHAKE_HOOKS_CERT_INVOKE,
HANDSHAKE_HOOKS_CLIENT_CERT,
HANDSHAKE_HOOKS_CLIENT_CERT_INVOKE,
HANDSHAKE_HOOKS_OUTBOUND_PRE,
HANDSHAKE_HOOKS_OUTBOUND_PRE_INVOKE,
HANDSHAKE_HOOKS_VERIFY_SERVER,
HANDSHAKE_HOOKS_DONE
} sslHandshakeHookState = HANDSHAKE_HOOKS_PRE;
int64_t redoWriteSize = 0;
char *tunnel_host = nullptr;
in_port_t tunnel_port = 0;
bool tunnel_decrypt = false;
X509_STORE_CTX *verify_cert = nullptr;
// Null-terminated string, or nullptr if there is no SNI server name.
std::unique_ptr<char[]> _serverName;
};
typedef int (SSLNetVConnection::*SSLNetVConnHandler)(int, void *);
extern ClassAllocator<SSLNetVConnection> sslNetVCAllocator;