restored/socket_tls.inc
← All source files · Download this file
7,734 bytes · SHA256 63f5f0cb02acd29a2dfb20863c1a5642943e86e801dfda2e2085479452c1868b
// Verified client transport. Custom BIO never changes process-wide signal policy.
struct SocketTLS {
SSL_CTX* context=nullptr;
SSL* ssl=nullptr;
int fd=-1;
std::function<void()> releaseResources;
~SocketTLS() { SSL_free(ssl); SSL_CTX_free(context); }
static int create(BIO* bio) { BIO_set_init(bio,1); return 1; }
static int destroy(BIO*) { return 1; } // Socket owns the descriptor.
static int read(BIO* bio,char* bytes,int size) {
BIO_clear_retry_flags(bio);
int fd=*static_cast<int*>(BIO_get_data(bio));
auto n=::recv(fd,bytes,static_cast<std::size_t>(size),0);
if(n<0 && (errno==EINTR || errno==EAGAIN || errno==EWOULDBLOCK)) BIO_set_retry_read(bio);
return static_cast<int>(n);
}
static int write(BIO* bio,const char* bytes,int size) {
BIO_clear_retry_flags(bio);
int fd=*static_cast<int*>(BIO_get_data(bio));
auto n=::send(fd,bytes,static_cast<std::size_t>(size),MSG_NOSIGNAL);
if(n<0 && (errno==EINTR || errno==EAGAIN || errno==EWOULDBLOCK)) BIO_set_retry_write(bio);
return static_cast<int>(n);
}
static long control(BIO*,int command,long,void*) { return command==BIO_CTRL_FLUSH ? 1 : 0; }
static BIO_METHOD* method() {
struct Method {
BIO_METHOD* value=BIO_meth_new(BIO_TYPE_SOURCE_SINK|BIO_get_new_index(),"Vanilla nonblocking socket");
Method() {
if(value && !(BIO_meth_set_create(value,create) && BIO_meth_set_destroy(value,destroy) &&
BIO_meth_set_read(value,read) && BIO_meth_set_write(value,write) && BIO_meth_set_ctrl(value,control))) {
BIO_meth_free(value); value=nullptr;
}
}
~Method() { BIO_meth_free(value); }
};
static Method method;
return method.value;
}
[[noreturn]] void error(const Source& s,const char* message,int systemError=errno) {
bool memory=false;
while(auto e=ERR_get_error()) if(ERR_GET_REASON(e)==ERR_R_MALLOC_FAILURE) memory=true;
if(memory || systemError==ENOMEM || systemError==ENOBUFS || systemError==EMFILE || systemError==ENFILE) {
releaseResources(); // Free descriptors before constructing diagnostics.
failLimit(s,"socket TLS resource limit");
}
fail(s,message);
}
// Retry within this call only: caller invalidates the handle on any exception.
int perform(int operation,void* bytes,int size,SocketClock::time_point deadline,const Source& s) {
for(;;) {
socketDeadline(deadline,s); ERR_clear_error(); errno=0;
int n=operation==0 ? SSL_connect(ssl) : operation==1 ? SSL_read(ssl,bytes,size) : SSL_write(ssl,bytes,size);
int systemError=errno;
if(n>0) return n;
int reason=SSL_get_error(ssl,n);
if(reason==SSL_ERROR_ZERO_RETURN && operation==1) return 0;
if(reason==SSL_ERROR_WANT_READ || reason==SSL_ERROR_WANT_WRITE) {
// First attempt always precedes poll, consuming SSL_pending data.
socketWait(fd,reason==SSL_ERROR_WANT_READ ? POLLIN : POLLOUT,deadline,s); continue;
}
error(s,operation==0 ? "socket TLS handshake or certificate verification failed" : "socket TLS transport failed (possible truncated stream)",systemError);
}
}
void connect(int descriptor,const std::string& host,const Options& options,SocketClock::time_point deadline,const Source& s) {
fd=descriptor; ERR_clear_error(); context=SSL_CTX_new(TLS_client_method());
if(!context) error(s,"socket TLS context failed");
SSL_CTX_set_verify(context,SSL_VERIFY_PEER,nullptr);
SSL_CTX_set_verify_depth(context,8);
SSL_CTX_set_options(context,SSL_OP_NO_COMPRESSION|SSL_OP_NO_RENEGOTIATION);
SSL_CTX_set_session_cache_mode(context,SSL_SESS_CACHE_OFF);
SSL_CTX_set_max_cert_list(context,128*1024);
if(!SSL_CTX_set_min_proto_version(context,TLS1_2_VERSION)) error(s,"socket TLS version configuration failed");
int trusted=options.tlsCAFile.empty() ? SSL_CTX_set_default_verify_paths(context) : SSL_CTX_load_verify_locations(context,options.tlsCAFile.c_str(),nullptr);
if(trusted!=1) error(s,"socket TLS trust configuration failed");
ssl=SSL_new(context);
if(!ssl) error(s,"socket TLS allocation failed");
SSL_set_mode(ssl,SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_set_hostflags(ssl,X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
if(socketNumeric(host,1).length) {
if(X509_VERIFY_PARAM_set1_ip_asc(SSL_get0_param(ssl),host.c_str())!=1) error(s,"socket TLS IP configuration failed");
} else if(SSL_set1_host(ssl,host.c_str())!=1 || SSL_set_tlsext_host_name(ssl,host.c_str())!=1)
error(s,"socket TLS name configuration failed");
auto* m=method(); if(!m) error(s,"socket TLS BIO allocation failed");
BIO* bio=BIO_new(m); if(!bio) error(s,"socket TLS BIO allocation failed");
BIO_set_data(bio,&fd); SSL_set_bio(ssl,bio,bio);
perform(0,nullptr,0,deadline,s); socketDeadline(deadline,s);
if(SSL_get_verify_result(ssl)!=X509_V_OK || !SSL_get0_peer_certificate(ssl)) error(s,"socket TLS certificate verification failed");
}
// One library call only. Owned caller storage survives every WANT retry.
int step(int operation,void* bytes,int size,short& want,const Source& s) {
ERR_clear_error(); errno=0;
int n=operation==3 ? SSL_accept(ssl) : operation==1 ? SSL_read(ssl,bytes,size) : SSL_write(ssl,bytes,size);
int systemError=errno;
if(n>0) { want=0; return n; }
int reason=SSL_get_error(ssl,n);
if(reason==SSL_ERROR_ZERO_RETURN && operation==1) { want=0; return 0; }
if(reason==SSL_ERROR_WANT_READ || reason==SSL_ERROR_WANT_WRITE) {
want=reason==SSL_ERROR_WANT_READ ? POLLIN : POLLOUT; return -1;
}
error(s,operation==3 ? "socket TLS handshake failed" : "socket TLS transport failed (possible truncated stream)",systemError);
}
static int noPassword(char*,int,int,void*) { return 0; }
void serverContext(const Options& options,const Source& s) {
ERR_clear_error(); errno=0; context=SSL_CTX_new(TLS_server_method());
if(!context) error(s,"socket TLS server context failed");
SSL_CTX_set_default_passwd_cb(context,noPassword);
SSL_CTX_set_options(context,SSL_OP_NO_COMPRESSION|SSL_OP_NO_RENEGOTIATION);
SSL_CTX_set_session_cache_mode(context,SSL_SESS_CACHE_OFF);
SSL_CTX_set_max_cert_list(context,128*1024);
if(!SSL_CTX_set_min_proto_version(context,TLS1_2_VERSION) ||
SSL_CTX_use_certificate_chain_file(context,options.tlsCertFile.c_str())!=1 ||
SSL_CTX_use_PrivateKey_file(context,options.tlsKeyFile.c_str(),SSL_FILETYPE_PEM)!=1 ||
SSL_CTX_check_private_key(context)!=1) error(s,"socket TLS server certificate/key configuration failed");
}
void serverConnection(int descriptor,SSL_CTX* parent,const Source& s) {
fd=descriptor; ERR_clear_error(); errno=0;
ssl=SSL_new(parent); // SSL owns a context reference, independent of listener.
if(!ssl) error(s,"socket TLS allocation failed");
SSL_set_mode(ssl,SSL_MODE_ENABLE_PARTIAL_WRITE);
auto* m=method(); if(!m) error(s,"socket TLS BIO allocation failed");
BIO* bio=BIO_new(m); if(!bio) error(s,"socket TLS BIO allocation failed");
BIO_set_data(bio,&fd); SSL_set_bio(ssl,bio,bio); SSL_set_accept_state(ssl);
}
void close() noexcept { if(ssl) { ERR_clear_error(); (void)SSL_shutdown(ssl); ERR_clear_error(); } }
};