v. vanilla 2.0

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(); } }
};