static apr_status_t ssl_io_filter_handshake()

in modules/ssl/ssl_engine_io.c [1166:1541]


static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx)
{
    conn_rec *c         = (conn_rec *)SSL_get_app_data(filter_ctx->pssl);
    SSLConnRec *sslconn = myConnConfig(c);
    SSLSrvConfigRec *sc;
    X509 *cert;
    int n;
    int ssl_err;
    long verify_result;
    server_rec *server;

    if (SSL_is_init_finished(filter_ctx->pssl)) {
        return APR_SUCCESS;
    }

    server = sslconn->server;
    if (c->outgoing) {
#ifdef HAVE_TLSEXT
        apr_ipsubnet_t *ip;
#endif
        const char *hostname_note = apr_table_get(c->notes,
                                                  "proxy-request-hostname");
#ifdef HAVE_TLS_ALPN
        const char *alpn_note;
        apr_array_header_t *alpn_proposed = NULL;
        int alpn_empty_ok = 1;
#endif
        BOOL proxy_ssl_check_peer_ok = TRUE;
        int post_handshake_rc = OK;
        SSLDirConfigRec *dc;

        dc = sslconn->dc;
        sc = mySrvConfig(server);

#ifdef HAVE_TLSEXT
#ifdef HAVE_TLS_ALPN
        alpn_note = apr_table_get(c->notes, "proxy-request-alpn-protos");
        if (alpn_note) {
            char *protos, *s, *p, *last, *proto;
            apr_size_t len;

            /* Transform the note into a protocol formatted byte array:
             * (len-byte proto-char+)*
             * We need the remote server to agree on one of these, unless 'http/1.1'
             * is also among our proposals. Because pre-ALPN remotes will speak this.
             */
            alpn_proposed = apr_array_make(c->pool, 3, sizeof(const char*));
            alpn_empty_ok = 0;
            s = protos = apr_pcalloc(c->pool, strlen(alpn_note)+1);
            p = apr_pstrdup(c->pool, alpn_note);
            while ((p = apr_strtok(p, ", ", &last))) {
                len = last - p - (*last? 1 : 0); 
                if (len > 255) {
                    ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03309)
                                  "ALPN proxy protocol identifier too long: %s",
                                  p);
                    ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, server);
                    return APR_EGENERAL;
                }
                proto = apr_pstrndup(c->pool, p, len);
                APR_ARRAY_PUSH(alpn_proposed, const char*) = proto;
                if (!strcmp("http/1.1", proto)) {
                    alpn_empty_ok = 1;
                }
                *s++ = (unsigned char)len;
                while (len--) {
                    *s++ = *p++;
                }
                p = NULL;
            }
            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, 
                          "setting alpn protos from '%s', protolen=%d", 
                          alpn_note, (int)(s - protos));
            if (protos != s && SSL_set_alpn_protos(filter_ctx->pssl, 
                                                   (unsigned char *)protos, 
                                                   s - protos)) {
                ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(03310)
                              "error setting alpn protos from '%s'", alpn_note);
                ssl_log_ssl_error(SSLLOG_MARK, APLOG_WARNING, server);
                /* If ALPN was requested and we cannot do it, we must fail */
                return MODSSL_ERROR_BAD_GATEWAY;
            }
        }
#endif /* defined HAVE_TLS_ALPN */
        /*
         * Enable SNI for backend requests. Make sure we don't do it for
         * pure SSLv3 connections, and also prevent IP addresses
         * from being included in the SNI extension. (OpenSSL would simply
         * pass them on, but RFC 6066 is quite clear on this: "Literal
         * IPv4 and IPv6 addresses are not permitted".)
         */
        if (hostname_note &&
#ifndef OPENSSL_NO_SSL3
            dc->proxy->protocol != SSL_PROTOCOL_SSLV3 &&
#endif
            apr_ipsubnet_create(&ip, hostname_note, NULL,
                                c->pool) != APR_SUCCESS) {
            if (SSL_set_tlsext_host_name(filter_ctx->pssl, hostname_note)) {
                ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c,
                              "SNI extension for SSL Proxy request set to '%s'",
                              hostname_note);
            } else {
                ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(02002)
                              "Failed to set SNI extension for SSL Proxy "
                              "request to '%s'", hostname_note);
                ssl_log_ssl_error(SSLLOG_MARK, APLOG_WARNING, server);
            }
        }
#endif /* defined HAVE_TLSEXT */

        if ((n = SSL_connect(filter_ctx->pssl)) <= 0) {
            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02003)
                          "SSL Proxy connect failed");
            ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server);
            /* ensure that the SSL structures etc are freed, etc: */
            ssl_filter_io_shutdown(filter_ctx, c, 1);
            apr_table_setn(c->notes, "SSL_connect_rv", "err");
            return MODSSL_ERROR_BAD_GATEWAY;
        }

        cert = SSL_get_peer_certificate(filter_ctx->pssl);

        if (dc->proxy->ssl_check_peer_expire != FALSE) {
            if (!cert
                || (X509_cmp_current_time(
                     X509_get_notBefore(cert)) >= 0)
                || (X509_cmp_current_time(
                     X509_get_notAfter(cert)) <= 0)) {
                proxy_ssl_check_peer_ok = FALSE;
                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02004)
                              "SSL Proxy: Peer certificate is expired");
            }
        }
        if ((dc->proxy->ssl_check_peer_name != FALSE) &&
            ((dc->proxy->ssl_check_peer_cn != FALSE) ||
             (dc->proxy->ssl_check_peer_name == TRUE)) &&
            hostname_note) {
            if (!cert
                || modssl_X509_match_name(c->pool, cert, hostname_note,
                                          TRUE, server) == FALSE) {
                proxy_ssl_check_peer_ok = FALSE;
                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02411)
                              "SSL Proxy: Peer certificate does not match "
                              "for hostname %s", hostname_note);
            }
        }
        else if ((dc->proxy->ssl_check_peer_cn == TRUE) &&
            hostname_note) {
            const char *hostname;
            int match = 0;

            hostname = ssl_var_lookup(c->pool, server, c, NULL,
                                      "SSL_CLIENT_S_DN_CN");

            /* Do string match or simplest wildcard match if that
             * fails. */
            match = strcasecmp(hostname, hostname_note) == 0;
            if (!match && strncmp(hostname, "*.", 2) == 0) {
                const char *p = ap_strchr_c(hostname_note, '.');
                
                match = p && strcasecmp(p, hostname + 1) == 0;
            }

            if (!match) {
                proxy_ssl_check_peer_ok = FALSE;
                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02005)
                              "SSL Proxy: Peer certificate CN mismatch:"
                              " Certificate CN: %s Requested hostname: %s",
                              hostname, hostname_note);
            }
        }

#ifdef HAVE_TLS_ALPN
        /* If we proposed ALPN protocol(s), we need to check if the server
         * agreed to one of them. While <https://www.rfc-editor.org/rfc/rfc7301.txt>
         * chapter 3.2 says the server SHALL error the handshake in such a case,
         * the reality is that some servers fall back to their default, e.g. http/1.1.
         * (we also do this right now)
         * We need to treat this as an error for security reasons.
         */
        if (alpn_proposed && alpn_proposed->nelts > 0) {
            const char *selected;
            unsigned int slen;

            SSL_get0_alpn_selected(filter_ctx->pssl, (const unsigned char**)&selected, &slen);
            if (!selected || !slen) {
                /* No ALPN selection reported by the remote server. This could mean
                 * it does not support ALPN (old server) or that it does not support
                 * any of our proposals (Apache itself up to 2.4.48 at least did that). */
               if (!alpn_empty_ok) {
                    ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10273)
                                  "SSL Proxy: Peer did not select any of our ALPN protocols [%s].",
                                  alpn_note);
                    proxy_ssl_check_peer_ok = FALSE;
               }
            }
            else {
                const char *proto;
                int i, found = 0;
                for (i = 0; !found && i < alpn_proposed->nelts; ++i) {
                    proto = APR_ARRAY_IDX(alpn_proposed, i, const char *);
                    found = !strncmp(selected, proto, slen);
                }
                if (!found) {
                    /* From a conforming peer, this should never happen,
                     * but life always finds a way... */
                    proto = apr_pstrndup(c->pool, selected, slen);
                    ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10274)
                                  "SSL Proxy: Peer proposed ALPN protocol %s which is none "
                                  "of our proposals [%s].", proto, alpn_note);
                    proxy_ssl_check_peer_ok = FALSE;
                }
            }
        }
#endif

        if (proxy_ssl_check_peer_ok == TRUE) {
            /* another chance to fail */
            post_handshake_rc = ssl_run_proxy_post_handshake(c, filter_ctx->pssl);
        }

        if (cert) {
            X509_free(cert);
        }

        if (proxy_ssl_check_peer_ok != TRUE
            || (post_handshake_rc != OK && post_handshake_rc != DECLINED)) {
            /* ensure that the SSL structures etc are freed, etc: */
            ssl_filter_io_shutdown(filter_ctx, c, 1);
            apr_table_setn(c->notes, "SSL_connect_rv", "err");
            return MODSSL_ERROR_BAD_GATEWAY;
        }

        apr_table_setn(c->notes, "SSL_connect_rv", "ok");
        return APR_SUCCESS;
    }

    /* We rely on SSL_get_error() after the accept, which requires an empty
     * error queue before the accept in order to work properly.
     */
    ERR_clear_error();

    if ((n = SSL_accept(filter_ctx->pssl)) <= 0) {
        bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)
                                     BIO_get_data(filter_ctx->pbioRead);
        bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)
                                       BIO_get_data(filter_ctx->pbioWrite);
        apr_status_t rc = inctx->rc ? inctx->rc : outctx->rc ;
        ssl_err = SSL_get_error(filter_ctx->pssl, n);

        if (ssl_err == SSL_ERROR_ZERO_RETURN) {
            /*
             * The case where the connection was closed before any data
             * was transferred. That's not a real error and can occur
             * sporadically with some clients.
             */
            ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, c, APLOGNO(02006)
                         "SSL handshake stopped: connection was closed");
        }
        else if (ssl_err == SSL_ERROR_WANT_READ) {
            /*
             * This is in addition to what was present earlier. It is
             * borrowed from openssl_state_machine.c [mod_tls].
             * TBD.
             */
            outctx->rc = APR_EAGAIN;
            return APR_EAGAIN;
        }
        else if (ERR_GET_LIB(ERR_peek_error()) == ERR_LIB_SSL &&
                 ERR_GET_REASON(ERR_peek_error()) == SSL_R_HTTP_REQUEST) {
            /*
             * The case where OpenSSL has recognized a HTTP request:
             * This means the client speaks plain HTTP on our HTTPS port.
             * ssl_io_filter_error will disable the ssl filters when it
             * sees this status code.
             */
            return MODSSL_ERROR_HTTP_ON_HTTPS;
        }
        else if (ssl_err == SSL_ERROR_SYSCALL) {
            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rc, c, APLOGNO(02007)
                          "SSL handshake interrupted by system "
                          "[Hint: Stop button pressed in browser?!]");
        }
        else /* if (ssl_err == SSL_ERROR_SSL) */ {
            /*
             * Log SSL errors and any unexpected conditions.
             */
            ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, c, APLOGNO(02008)
                          "SSL library error %d in handshake "
                          "(server %s)", ssl_err,
                          ssl_util_vhostid(c->pool, server));
            ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server);

        }
        if (inctx->rc == APR_SUCCESS) {
            inctx->rc = APR_EGENERAL;
        }

        ssl_filter_io_shutdown(filter_ctx, c, 1);
        return inctx->rc;
    }
    sc = mySrvConfig(sslconn->server);

    /*
     * Check for failed client authentication
     */
    verify_result = SSL_get_verify_result(filter_ctx->pssl);

    if ((verify_result != X509_V_OK) ||
        sslconn->verify_error)
    {
        if (ssl_verify_error_is_optional(verify_result) &&
            (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
        {
            /* leaving this log message as an error for the moment,
             * according to the mod_ssl docs:
             * "level optional_no_ca is actually against the idea
             *  of authentication (but can be used to establish
             * SSL test pages, etc.)"
             * optional_no_ca doesn't appear to work as advertised
             * in 1.x
             */
            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02009)
                          "SSL client authentication failed, "
                          "accepting certificate based on "
                          "\"SSLVerifyClient optional_no_ca\" "
                          "configuration");
            ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server);

            /* on session resumption ssl_callback_SSLVerify() 
             * will not be called, therefore we have to set it here
             */
            sslconn->verify_info = "GENEROUS";
        }
        else {
            const char *error = sslconn->verify_error ?
                sslconn->verify_error :
                X509_verify_cert_error_string(verify_result);

            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02010)
                         "SSL client authentication failed: %s",
                         error ? error : "unknown");
            ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server);

            ssl_filter_io_shutdown(filter_ctx, c, 1);
            return APR_ECONNABORTED;
        }
    }

    /*
     * Remember the peer certificate's DN
     */
    if ((cert = SSL_get_peer_certificate(filter_ctx->pssl))) {
        if (sslconn->client_cert) {
            X509_free(sslconn->client_cert);
        }
        sslconn->client_cert = cert;
        sslconn->client_dn = NULL;
    }

    /*
     * Make really sure that when a peer certificate
     * is required we really got one... (be paranoid)
     */
    if ((sc->server->auth.verify_mode == SSL_CVERIFY_REQUIRE) &&
        !sslconn->client_cert)
    {
        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02011)
                      "No acceptable peer certificate available");

        ssl_filter_io_shutdown(filter_ctx, c, 1);
        return APR_ECONNABORTED;
    }

    return APR_SUCCESS;
}