Bug 55707

Summary: SSLProtocol directive seem to be ignored over different virtualhosts on the same ip+port
Product: Apache httpd-2 Reporter: cf0hay
Component: mod_sslAssignee: Apache HTTPD Bugs Mailing List <bugs>
Status: REOPENED ---    
Severity: normal CC: apache_bugzilla, bruno.canet, c.affolter, chuck.t.parker, dan, f.niepelt, martin, mike, nsg-apache-httpd-maintenance, szg0000, thomas.altenburger
Priority: P2 Keywords: PatchAvailable
Version: 2.4.37   
Target Milestone: ---   
Hardware: All   
OS: All   
Attachments: Reject connections not conforming to vhost SSLProtocol

Description cf0hay 2013-10-25 19:35:22 UTC
I have more than one virtualhosts configured over the same IP address and port. The first one has these directives (uses RSA):

SSLProtocol TLSv1.2 +TLSv1.1 +TLSv1
SSLCipherSuite ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DES-CBC3-SHA

The second one only these (uses EC):

SSLProtocol TLSv1.2
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA
SSLStrictSNIVHostCheck on

Non-SNI clients get 403 properly. But if a client supports SNI, and negotiates with TLSv1.1 or TLSv1, the request will be accepted and the page served. With an SNI client, the SSLCipherSuite list will get used properly, but the SSLProtocol directive is totally ignored. From the ClientHello the SNI-capability can be detected, so does the used protocol version. TLS negotiation should be denied the same way if there is no common protocol version as it would without common ciphers.
Comment 1 Kaspar Brand 2013-11-30 08:06:17 UTC
Confirmed. It's due to a current limitation in OpenSSL, actually - when we use  SSL_set_SSL_CTX() in the SNI callback in ssl_engine_kernel.c, OpenSSL only switches the certificate, but not any of the other settings.

I have filed

  https://rt.openssl.org/Ticket/Display.html?user=guest&pass=guest&id=3183

meanwhile, and on the mod_ssl side, we are basically stuck with fixing this in a sane way until it's addressed in OpenSSL. If you're compiling against OpenSSL before 1.0.0, you could apply this patch, in theory (but note that this fiddles with OpenSSL internals, and is not the way forward to fix the issue):

--- ssl_engine_kernel.c (revision 1546692)
+++ ssl_engine_kernel.c (working copy)
@@ -2014,6 +2014,7 @@ static int ssl_find_vhost(void *servername, conn_r
             SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ctx),
                            SSL_CTX_get_verify_callback(ctx));
         }
+        SSL_set_ssl_method(ssl, ctx->method);

         /*
          * Adjust the session id context. ssl_init_ssl_connection()
Comment 2 Charles Parker 2017-05-16 12:10:45 UTC
(In reply to Kaspar Brand from comment #1)
> Confirmed. It's due to a current limitation in OpenSSL, actually - when we
> use  SSL_set_SSL_CTX() in the SNI callback in ssl_engine_kernel.c, OpenSSL
> only switches the certificate, but not any of the other settings.
> 
> I have filed
> 
>   https://rt.openssl.org/Ticket/Display.html?user=guest&pass=guest&id=3183
> 

This OpenSSL bug was marked as resolved on October 13, 2016. I don't see any indication of which version(s) contain the fix, however.
Comment 3 Rainer Jung 2017-05-16 21:34:31 UTC
The OpenSSL RT ticket was only closed as part of a mass update of old ticket. From a mail to the OpenSSL dev list:

Here is the list of old RT tickets that we are closing.  We sent email
to all of the originators, and it included the following text:

If you still think it is important for us to consider, please open an
issue on GitHub.  Don't be shy!  We are closing issues based purely
on the date, and the fact that despite several passes through our RT
tickets, we haven't closed them yet.

...

3183 SSL_set_SSL_CTX() should apply more settings from the SSL_CTX being switched to
...

So this wasn't actually fixed, at least not at that time.

Regards,

Rainer
Comment 4 Abir Ahmed 2017-09-01 00:15:32 UTC
Hey guys,

It's been a long time that it has been an issue. I am having the same trouble.

I contacted openssl git page and they said it's because how Apache handles openssl.

I tried with both 1.0.2 and 1.1.0 and it's still not working.

Is there any workaround I can apply? It's pretty urgent.

Thank you
Comment 5 nada 2017-09-01 09:31:14 UTC
Just for the reference: here is the re-reported issue for OpenSSL on GitHub: https://github.com/openssl/openssl/issues/4302
Comment 6 Abir Ahmed 2017-09-01 17:07:27 UTC
This is the issue where they replied. See their latest response.

Link: https://github.com/openssl/openssl/issues/4301
Comment 7 Mike Haller 2018-04-09 05:06:53 UTC
Created attachment 35848 [details]
Reject connections not conforming to vhost SSLProtocol

This was developed and tested with 2.4.27 and in production with that
version.  The patch was modified for 2.4.33 and lightly tested.

This checks the version of the connection against the SSLProtocol
configured for the virtual host that is matched based on the SNI.
Because the connection is initially made with the SSLProtocol configured
for the default host for the port, the default host must include all
protocols that will be supported by any virtual host.

This patch adds an additional return status of APR_EMISMATCH to the
init_vhost function so that the ssl_callback_ServerNameIndication
callback registered with OpenSSL can return fatal alert
SSL_AD_PROTOCOL_VERSION. This is intended to produce the same response
to the ClientHello as having an SSLProtocol specified that does not
include the version in question.  Because the SNI callback is called
during the processing of the ClientHello and before a response is
produced, it seems to do exactly that.

Feedback is welcome.
Comment 8 Stefan Eissing 2018-04-11 08:43:07 UTC
Feedback on the patch:

Yes, this will work and prevent connections to a vhost with an SSL protocol version that is not allowed there. However.

When I imagine being the user who typed a URL into my browser - and ran into this behaviour - what would I be expected to do? Is there, in this setup, a way to successfully connect to the vhost? I don't see it.

If we can agree that this is an undesirable situation, the only fix - besides re-implementing mod_ssl and vhosts in a non 2.4.x compatible way - is that the *server admin* gets an ERR/WARNING by a post config check in mod_ssl.

(Since we are talking about fixes in 2.4.x, WARNING is the only option unless we introduce a "SSLVHostChecks strict" or some such.)

So, IMO, the patch is good, but not enough.

Feedback?
Comment 9 Mike Haller 2018-04-11 14:45:12 UTC
Thanks for looking Stefan.

> Is there, in this setup, a way to successfully connect to the vhost?

The point of the patch is to prevent a successful connection to the vhost at
the TLS protocol layer. If that's not what you want and you instead wish to
produce a friendly error message for some versions, you can already configure
mod_ssl to accept all versions, and to publish the SSL_PROTOCOL env var, and
then use any number of ways (e.g. rewrite, setenvif) to produce an error page
if there is a version you do not wish to accept.

> ... the *server admin* gets an ERR/WARNING by a post config check in
> mod_ssl.

From a user's perspective, they see behavior no different than if they attempt
to connect with a TLS version that is not specified in the default server's
SSLProtocol: a protocol version alert. Is the startup warning suggestion
because this patch changes the existing behavior that current configurations
will accept versions that are not allowed by a vhost's SSLProtocol?
Comment 10 Stefan Eissing 2018-04-12 09:17:19 UTC
I am fine with the patch. What I am after is: is a configuration that triggers the patch not really a configuration error?

Simplified:
<vhost A>
  SSLProtocol TLSv1.2
</vhost>

<vhost B>
  SSLProtocol TLSv1.3
</vhost>

Is a client that speaks both TLSv1.2 and TLSv1.3 able to connect to vhost B at all?
Comment 11 Mike Haller 2018-04-12 14:16:53 UTC
> Is a client that speaks both TLSv1.2 and TLSv1.3 able to connect to vhost B
> at all?

Yes. Such a client could speak to both.

The case I am solving is this:

<vhost A>
  SSLProtocol +TLSV1 +TLSV1.1 +TLSV1.2
</vhost A>

<vhost B>
  SSLProtocol +TLSV1.2
</vhost B>

This configuration allows for vhost B to accept only the newest protocol
implemented in 2.4.33.

It will function as expected if you write "SSLProtocol +TLSV1.1" for vhost B.
However, I would not consider that a useful configuration because I think that
the concept "minimum TLS version needed to connect to this host" is more
likely than arbitrarily specifying versions. For example, PCI DSS (the credit
card security standard) is requiring that TLSv1 and TLSv1.1 can no longer be
used to connect after June 30, 2018, but allows for any newer protocols TLSv1.2, TLSv1.3, etc.
Comment 12 Stefan Eissing 2018-04-12 14:22:29 UTC
You are solving the puzzle by changing the puzzle. ;-)

Your example is valid and would be improved by the patch and work and no error should be logged. Okay?

Do you agree that in *my* example:
<vhost A>
  SSLProtocol TLSv1.2
</vhost>

<vhost B>
  SSLProtocol TLSv1.3
</vhost>

the configuration is in error and the admin should at least be WARNED that a connection to vhost B is not possible at all?
Comment 13 Mike Haller 2018-04-12 17:17:03 UTC
I made a mistake earlier in mentioning the default host. The default setting
appears to come from the root configuration ap_server_conf and not the default
server. The built-in default config is "all -SSLv3" which is why I state you
can connect to vhost B above. If your example is based on my mistake of
mentioning the default host, and we adapt it to:

SSLProtocol TLSv1.1
<vhost B>
  SSLProtocol TLSv1.2
</vhost>

Then this *does* lead to the situation where vhost B cannot be connected to.
And it does make sense to have a warning there.
Comment 14 Dennis Clarke 2018-05-28 07:55:35 UTC

Is testing possible with TLSv1.3 and Apache/2.4.33 (Unix) OpenSSL/1.1.1-pre6 ? 

I see the following error if I attempt to use a new tls 1.3 cipher from
latest beta openssl : 

AH00526: Syntax error on line 19 of /usr/local/www/conf/extra/httpd-ssl.conf:
SSLProtocol: Illegal protocol 'TLSv1.3'


# /usr/local/bin/openssl version 
OpenSSL 1.1.1-pre6 (beta) 1 May 2018

# /usr/local/bin/openssl ciphers -V -tls1_3 -s 
          0x13,0x02 - TLS_AES_256_GCM_SHA384  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(256) Mac=AEAD
          0x13,0x03 - TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any      Au=any  Enc=CHACHA20/POLY1305(256) Mac=AEAD
          0x13,0x01 - TLS_AES_128_GCM_SHA256  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(128) Mac=AEAD


However I should be able to run tests?
Comment 15 Dennis Clarke 2018-06-01 17:34:10 UTC
I am doing tests with TLSv1.3 on httpd-2.5-trunk per : 

    https://bz.apache.org/bugzilla/show_bug.cgi?id=62413

also :

    https://bz.apache.org/bugzilla/show_bug.cgi?id=62417

Thus far everything seems to be working fine with the obvious exception
that no browser can connect to the site. Yet. May be a CipherSuite 
issue wherein Mozilla FireFox ver 62.0a1 does support TLS1.3 and I have
tested this via https://tls13.crypto.mozilla.org/ which works fine and 
offers TLS 1.3 (draft 28) which seems to offer TLS_AES_128_GCM_SHA256
as the cipher.
Comment 16 Dan McLaughlin 2018-11-21 05:55:00 UTC
Is this issue supposed to be fix?  

I'm trying to be as detailed as possible to help, so don't skim my comments or notes below or you might miss important information. First I'll explain how things are configure, then at the bottom I'll explain the behavior we see.

We have some third party clients that use our web service that have not upgraded their applications so we have to temporarily support TLS 1.0 for them.  We are limited to only a single IP address and port because of infrastructure outside of our control.  We do have two domains however that are registered in DNS that point to the same external IP address.  What we are attempting to do is use the two domain names we have registered to support upgrading all of our clients that support TLS 1.2 & 1.3 using www.domain-new.com, while still providing temporary support for clients that still only support TLS 1.0 using www.domain-old.com. 

NOTE: There are couple things that I'll mention that aren't detailed in my example, but I'm mentioning them just in case you think they could somehow affect things.

1) <Location "/secure/myWebService">, <Location "/secure/AppA">, etc... are contexts that are hosted on our back-end application servers and are proxied/load balanced to the back-end Tomcat application servers using mod_jk.

2) HAProxy sits in front of Apache load balancing a farm of Apache webservers. We use mod_remoteip and send-proxy-v2 so that we can log the actual client IP address in the Apache access logs instead of the HAProxy IP address.

The HAProxy config looks like this:

...
listen https_proxy 
	bind 144.192.168.2:443
        mode tcp
        option tcplog
	option ssl-hello-chk
        balance roundrobin
        server http01 144.192.168.5:443 check-send-proxy send-proxy-v2
        server http02 144.192.168.6:443 check-send-proxy send-proxy-v2   

In each of our Virtual Hosts we configure mod_remoteip send-proxy-v2 support using:
    RemoteIPProxyProtocol On
    RemoteIPTrustedProxy 144.192.168.2


Here is the simplified version of what I'm running... 

Listen 144.192.168.5:443

<VirtualHost _default_:443>
    
#NOTE: I added _default_ only because I saw a comment earlier in this ticket
#where it was stated that I had to have a default host configured that
#supported all of the SSL protocols that I needed to support in all vhosts.
#Adding this hasn't changed the behavior at all. Things behave the same
#regardless, meaning if I remove the _default_ vhost I see no change in the
#behavior I see. I'm leaving it here only to show I've tried it.

    DocumentRoot "/docroot"
    RemoteIPProxyProtocol On
    RemoteIPTrustedProxy 192.168.0.2
    
    SSLEngine on
    SSLProtocol all
    SSLCipherSuite DEFAULT
    SSLStrictSNIVHostCheck on
    SSLCertificateFile /certs/www.domain-new.com.server.crt.pem
    SSLCertificateKeyFile /certs/www.domain-new.com.priv.key.pem
    SSLCACertificateFile /trustedCAs/cacerts.crt.pem
    SSLCARevocationFile /crls/ca-bundle.crl
    SSLVerifyClient none
    SSLVerifyDepth  10
</VirtualHost>

<VirtualHost 144.192.168.5:443>
    
#NOTE: We need to Support TLSv1 for Legacy Web Service Clients ie. .NET 3.5,
#JDK 6
#The plan was to point all Legacy Clients that only support TLS 1.0 to
#https://www.domain-old.com/secure/myWebService 
    
    ServerName www.domain-old.com
    RemoteIPProxyProtocol On
    RemoteIPTrustedProxy 192.168.0.2
    DocumentRoot "/docroot"
    
    SSLEngine on
    SSLProtocol TLSv1
    SSLCipherSuite DEFAULT
    SSLStrictSNIVHostCheck on
    SSLCertificateFile /certs/www.domain-old.com.server.crt.pem
    SSLCertificateKeyFile /certs/www.domain-old.com.priv.key.pem
    SSLCACertificateFile /trustedCAs/cacerts.crt.pem
    SSLCARevocationFile /crls/ca-bundle.crl
    SSLVerifyClient none
    SSLVerifyDepth  10

    # Because we only want to support TLS 1.0 for two customers we use mod_rewrite to rewrite anything that's not trying to hit /secure/myWebService to the virtual host for our primary domain where all of our applications are hosted on www.domain-new.com. Commenting this out makes no difference in the behavior we see. 

    RewriteEngine On
    RewriteCond %{REQUEST_URI} !^/secure/myWebService.* [NC]
    RewriteCond %{HTTP_HOST} www.\domain-old\.com$ [NC]
    RewriteRule ^ https://www.domain-new.com%{REQUEST_URI} [L,R=301] 

    <Location "/secure/myWebService">
      Require ip 143.33.44.43 201.23.45.3
    </Location>
</VirtualHost>

<VirtualHost 144.192.168.5:443>

#We need to support ONLY TLSv1.2 & TLSv1.3 for Newer Browsers and Web Service
#Clients ie. Latest Browsers, .NET 4.6.2, JDK 7u131 or greater
#The plan was to point all Newer Clients that support TLS 1.2 & 1.3 to
#https://www.domain-new.com/secure/myWebService

    ServerName www.domain-new.com
    RemoteIPProxyProtocol On
    RemoteIPTrustedProxy 192.168.0.2
    DocumentRoot "/docroot"

    SSLEngine on
    SSLProtocol SSLProtocol TLSv1.2 TLSv1.3
    SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
    SSLStrictSNIVHostCheck on
    SSLCertificateFile /certs/www.domain-new.com.server.crt.pem
    SSLCertificateKeyFile /certs/www.domain-new.com.priv.key.pem
    SSLCACertificateFile /trustedCAs/cacerts.crt.pem
    SSLCARevocationFile /crls/ca-bundle.crl
    SSLVerifyClient none
    SSLVerifyDepth  10

    <Location "/secure/myWebService">
      Require all granted
    </Location>

    <Location "/secure/myWebService">
      Require all granted
    </Location>

    <Location "/secure/AppA">
      Require all granted
    </Location>

    <Location "/secure/AppB">
      Require all granted
    </Location>

   <Location "/secure/AppC">
      Require all granted
    </Location>

    <Location "/secure/AppD">
      Require all granted
    </Location>
</VirtualHost>

What I was expecting is that if I hit https://www.domain-old.com/secure/myWebService that TLS 1.0 would be negotiated, and if I hit https://www.domain-new.com/secure/myWebService that TLS 1.3 or 1.2 would be negotiated.  That's not what happens. In the example above TLS 1.0 is what Chrome, FireFox, and Safari negotiate (all the latest versions), regardless if I connect to https://www.domain-old.com/secure/myWebService or https://www.domain-new.com/secure/myWebService.

In summary, what I'm seeing is that whatever the SSLProtocol is configured to in the first VirtualHost is the SSL protocol that is used for ALL VirtualHosts that share the same IP:Port.  If I change the order of the VirtualHosts, then the TLS version changes to whatever the first VirtualHost is. If don't change the order but instead change the SSLProtocol for www.domain-old.com VirtualHost to TLSv1.3 then all the VirtualHosts use TLSv1.3. 

Based on everything I've read in this ticket and the related OpenSSL tickets what I'm attempting to do should be working using the version of Apache and OpenSSL that I'm using, but its clearly not.  I'm running Apache/2.4.37 (Win64) OpenSSL/1.1.1 mod_jk/1.2.46 VC15 from ApacheLounge on Windows 2008 R2 SP1.  I'd much rather be running on this on Linux, but it's out of my control because of third-party integrations we have the require us to run on Windows.
Comment 17 Dan McLaughlin 2018-11-21 05:57:50 UTC
(In reply to Dan McLaughlin from comment #16)
> Is this issue supposed to be fix?  
> 
> I'm trying to be as detailed as possible to help, so don't skim my comments
> or notes below or you might miss important information. First I'll explain
> how things are configure, then at the bottom I'll explain the behavior we
> see.
> 
> We have some third party clients that use our web service that have not
> upgraded their applications so we have to temporarily support TLS 1.0 for
> them.  We are limited to only a single IP address and port because of
> infrastructure outside of our control.  We do have two domains however that
> are registered in DNS that point to the same external IP address.  What we
> are attempting to do is use the two domain names we have registered to
> support upgrading all of our clients that support TLS 1.2 & 1.3 using
> www.domain-new.com, while still providing temporary support for clients that
> still only support TLS 1.0 using www.domain-old.com. 
> 
> NOTE: There are couple things that I'll mention that aren't detailed in my
> example, but I'm mentioning them just in case you think they could somehow
> affect things.
> 
> 1) <Location "/secure/myWebService">, <Location "/secure/AppA">, etc... are
> contexts that are hosted on our back-end application servers and are
> proxied/load balanced to the back-end Tomcat application servers using
> mod_jk.
> 
> 2) HAProxy sits in front of Apache load balancing a farm of Apache
> webservers. We use mod_remoteip and send-proxy-v2 so that we can log the
> actual client IP address in the Apache access logs instead of the HAProxy IP
> address.
> 
> The HAProxy config looks like this:
> 
> ...
> listen https_proxy 
> 	bind 144.192.168.2:443
>         mode tcp
>         option tcplog
> 	option ssl-hello-chk
>         balance roundrobin
>         server http01 144.192.168.5:443 check-send-proxy send-proxy-v2
>         server http02 144.192.168.6:443 check-send-proxy send-proxy-v2   
> 
> In each of our Virtual Hosts we configure mod_remoteip send-proxy-v2 support
> using:
>     RemoteIPProxyProtocol On
>     RemoteIPTrustedProxy 144.192.168.2
> 
> 
> Here is the simplified version of what I'm running... 
> 
> Listen 144.192.168.5:443
> 
> <VirtualHost _default_:443>
>     
> #NOTE: I added _default_ only because I saw a comment earlier in this ticket
> #where it was stated that I had to have a default host configured that
> #supported all of the SSL protocols that I needed to support in all vhosts.
> #Adding this hasn't changed the behavior at all. Things behave the same
> #regardless, meaning if I remove the _default_ vhost I see no change in the
> #behavior I see. I'm leaving it here only to show I've tried it.
> 
>     DocumentRoot "/docroot"
>     RemoteIPProxyProtocol On
>     RemoteIPTrustedProxy 192.168.0.2
>     
>     SSLEngine on
>     SSLProtocol all
>     SSLCipherSuite DEFAULT
>     SSLStrictSNIVHostCheck on
>     SSLCertificateFile /certs/www.domain-new.com.server.crt.pem
>     SSLCertificateKeyFile /certs/www.domain-new.com.priv.key.pem
>     SSLCACertificateFile /trustedCAs/cacerts.crt.pem
>     SSLCARevocationFile /crls/ca-bundle.crl
>     SSLVerifyClient none
>     SSLVerifyDepth  10
> </VirtualHost>
> 
> <VirtualHost 144.192.168.5:443>
>     
> #NOTE: We need to Support TLSv1 for Legacy Web Service Clients ie. .NET 3.5,
> #JDK 6
> #The plan was to point all Legacy Clients that only support TLS 1.0 to
> #https://www.domain-old.com/secure/myWebService 
>     
>     ServerName www.domain-old.com
>     RemoteIPProxyProtocol On
>     RemoteIPTrustedProxy 192.168.0.2
>     DocumentRoot "/docroot"
>     
>     SSLEngine on
>     SSLProtocol TLSv1
>     SSLCipherSuite DEFAULT
>     SSLStrictSNIVHostCheck on
>     SSLCertificateFile /certs/www.domain-old.com.server.crt.pem
>     SSLCertificateKeyFile /certs/www.domain-old.com.priv.key.pem
>     SSLCACertificateFile /trustedCAs/cacerts.crt.pem
>     SSLCARevocationFile /crls/ca-bundle.crl
>     SSLVerifyClient none
>     SSLVerifyDepth  10
> 
>     # Because we only want to support TLS 1.0 for two customers we use
> mod_rewrite to rewrite anything that's not trying to hit
> /secure/myWebService to the virtual host for our primary domain where all of
> our applications are hosted on www.domain-new.com. Commenting this out makes
> no difference in the behavior we see. 
> 
>     RewriteEngine On
>     RewriteCond %{REQUEST_URI} !^/secure/myWebService.* [NC]
>     RewriteCond %{HTTP_HOST} www.\domain-old\.com$ [NC]
>     RewriteRule ^ https://www.domain-new.com%{REQUEST_URI} [L,R=301] 
> 
>     <Location "/secure/myWebService">
>       Require ip 143.33.44.43 201.23.45.3
>     </Location>
> </VirtualHost>
> 
> <VirtualHost 144.192.168.5:443>
> 
> #We need to support ONLY TLSv1.2 & TLSv1.3 for Newer Browsers and Web Service
> #Clients ie. Latest Browsers, .NET 4.6.2, JDK 7u131 or greater
> #The plan was to point all Newer Clients that support TLS 1.2 & 1.3 to
> #https://www.domain-new.com/secure/myWebService
> 
>     ServerName www.domain-new.com
>     RemoteIPProxyProtocol On
>     RemoteIPTrustedProxy 192.168.0.2
>     DocumentRoot "/docroot"
> 
>     SSLEngine on
>     SSLProtocol SSLProtocol TLSv1.2 TLSv1.3
>     SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
>     SSLStrictSNIVHostCheck on
>     SSLCertificateFile /certs/www.domain-new.com.server.crt.pem
>     SSLCertificateKeyFile /certs/www.domain-new.com.priv.key.pem
>     SSLCACertificateFile /trustedCAs/cacerts.crt.pem
>     SSLCARevocationFile /crls/ca-bundle.crl
>     SSLVerifyClient none
>     SSLVerifyDepth  10
> 
>     <Location "/secure/myWebService">
>       Require all granted
>     </Location>
> 
>     <Location "/secure/myWebService">
>       Require all granted
>     </Location>
> 
>     <Location "/secure/AppA">
>       Require all granted
>     </Location>
> 
>     <Location "/secure/AppB">
>       Require all granted
>     </Location>
> 
>    <Location "/secure/AppC">
>       Require all granted
>     </Location>
> 
>     <Location "/secure/AppD">
>       Require all granted
>     </Location>
> </VirtualHost>
> 
> What I was expecting is that if I hit
> https://www.domain-old.com/secure/myWebService that TLS 1.0 would be
> negotiated, and if I hit https://www.domain-new.com/secure/myWebService that
> TLS 1.3 or 1.2 would be negotiated.  That's not what happens. In the example
> above TLS 1.0 is what Chrome, FireFox, and Safari negotiate (all the latest
> versions), regardless if I connect to
> https://www.domain-old.com/secure/myWebService or
> https://www.domain-new.com/secure/myWebService.
> 
> In summary, what I'm seeing is that whatever the SSLProtocol is configured
> to in the first VirtualHost is the SSL protocol that is used for ALL
> VirtualHosts that share the same IP:Port.  If I change the order of the
> VirtualHosts, then the TLS version changes to whatever the first VirtualHost
> is. If don't change the order but instead change the SSLProtocol for
> www.domain-old.com VirtualHost to TLSv1.3 then all the VirtualHosts use
> TLSv1.3. 
> 
> Based on everything I've read in this ticket and the related OpenSSL tickets
> what I'm attempting to do should be working using the version of Apache and
> OpenSSL that I'm using, but its clearly not.  I'm running Apache/2.4.37
> (Win64) OpenSSL/1.1.1 mod_jk/1.2.46 VC15 from ApacheLounge on Windows 2008
> R2 SP1.  I'd much rather be running on this on Linux, but it's out of my
> control because of third-party integrations we have the require us to run on
> Windows.


All references to 192.168.0.2 above were a type, it was supposed to be 144.192.168.2.
Comment 18 staffan.hamala@metria.se 2019-02-19 13:42:50 UTC
Just upgraded to apache-2.4.38 from apache-2.2 and ran into the same bug. So it's not fixed yet.
Comment 19 staffan.hamala@metria.se 2019-02-19 13:54:23 UTC
Here, the problem was that all vhosts with the same ip only accepted TLS1.2, no matter that the config of some vhosts allows TLS1.0 and 1.1.

The only way to get TLS1.0 on one vhost was to add SSLProtocol ALL -SSLv2 -SSLv3 to all vhosts with the same ip.

This used to work on apache 2.2.

The interesting thing is that no vhost disabled all protocols. They had the following settings:
SSLProtocol ALL -SSLv2 -SSLv3
SSLProtocol -ALL +TLSv1.1 +TLSv1.2 +TLSv1.3
SSLProtocol -ALL +TLSv1 +TLSv1.2 +TLSv1.3
(some of them multiple times)

So, it seems like if one vhost disables TLSv1 and another disables TLSv1.1, all of the vhosts gets both TLSv1 and TLSv1.1 disabled.

This is bad because we want to disable TLSv1 everywhere possible. As far as I know, only one host really needs TLSv1. Now I had to enable TLSv1 on all vhosts (that share the same ip).
Comment 20 Jani 2019-03-03 16:32:05 UTC
I can confirm this bug too. In my case, Apache does not honour the "SSLCipherSuite TLSv1.3" directive for non-default virtual hosts. Instead it inherits the setting from the default virtual host. Please not that ONLY the TLSv1.3 cipher suite list is ignored; for all other protocols, the cipher suite list is respected (SSLCipherSuite directive). Example:

Configure default virtual host (mydomain.com) with the following TLS settings. We put AES-256-GCM at the top for corporate compliance as it is the strongest current cipher:

SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite		ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
SSLCipherSuite TLSv1.3	TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256

Configure another virtual host (myotherdomain.net) with the following TLS settings/ We want AES-128-GCM at the top for this virtual host because we do not need compliance, AES-128-GCM is much faster than AES-256-GCM and the extra speed is more important to us than the additional security:

SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite TLSv1.3	TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384
SSLCipherSuite		ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384

Results:
mydomain.com: cipher suites are in correct order for both TLS 1.2 and TLS 1.3
myotherdomain.net: cipher suites are in correct order for TLS 1.2, but the order has been ignored for TLS 1.3, and has inherited the order from the configuration of mydomain.com. So AES-128-GCM is first for TLS 1.2 clients, but AES-256-GCM is first (should be last!) for TLS 1.3 clients.

If we make myotherdomain.net the default virtual host, the cipher suite order is honoured for both TLS 1.2 and 1.3, but then the TLS 1.3 cipher order is broken for mydomain.com.
Comment 21 William A. Rowe Jr. 2019-03-05 21:05:08 UTC
"This is bad because we want to disable TLSv1 everywhere possible. As far as I
know, only one host really needs TLSv1. Now I had to enable TLSv1 on all vhosts (that share the same ip)."

It's not bad, it is correct. You need to move the named host off of the 
IP which you wish to lock down to TLSv1.1. Doing anything else is 
circumventing the security policy you were given to enforce.

The handshake dictated by SSLProtocol is not an authnz restriction, it is
the hardwired handshake exchanged by the client and server. One and only
one configuration applies, which is the one in the first-matched vhost
found for the given IP:Port (otherwise known as the connection or physical
vhost.)

If you want to refuse traffic afterwards, clever use of SSL variables can
let you evaluate the negotiated protocol, but that's beyond the scope of
mod_ssl's task.

To Stefan's question, the confg would look like

<vhost A0>
  SSLProtocol +TLSV1.1 +TLSV1.2
</vhost A0>

<vhost A>
  SSLProtocol +TLSV1.1
</vhost A>

<vhost B>
  SSLProtocol +TLSV1.2
</vhost B>

But that is still nonsense. Confusing the protocol with authnz is going to
lead to further configuration confusion. Detecting and alerting mismatched
SSLProtocol configs between same physical vhosts would be more helpful for
users trying to avoid misconfigs.

It seems the reporter desires SSLRequireSSL TLSV1.0/SSLRequireSSL TLSV1.2
in the respective hosts, which is not supported today. This would be very
radically different from SSLProtocol, because we can't suddenly up and
disconnect the TLSv1.0 connection to the second host without confusing the
client and therefore the user, but we can't send an error response on the 
second host if the security policy prohibits all TLSv1.0/1.1 traffic.

SSLProtocol +TLSv1 +TLSV1.1 +TLSV1.2
SSLRequireSSL TLSV1.2

would be unambiguous.

The right solution, not mentioned before in this thread, is simply using
two different IP:Port assignments for traffic which must follow a TLSv1.2
security policy, and the alternate assignment for traffic allows legacy
protocols.
Comment 22 Mike Haller 2019-03-05 21:29:41 UTC
I think your argument could apply equally to the server name from SNI since both it the client version arrive in the same ClientHello.
Comment 23 William A. Rowe Jr. 2019-03-05 23:12:32 UTC
Re comment #21, I think you are suggesting the correspondence of SNI and
SSLCipherSuite? The client hello is processed by invoking the OpenSSL read 
client hello which corresponds to the SSLProtocol defined by the first 
(physical matching) IP:Port vhost. Once that happens I guess we can trust
the version reported as you suggest, provided that processing all occurs
prior to the server hello. If the SNI callback is being invoked before 
the server hello, I believe your patch may be sufficient. (It is currently
missing SSLV3 and TLSV1_3 logic, easy fixes.) I'd be concerned about future
maintenance, but there are enough other places we can trip over TLSV1_4 that
it probably isn't a fair objection.

I think we can document the somewhat crazy example Stefan was looking for;

<vhost A0>
  SSLProtocol +TLSV1.1 +TLSV1.2
</vhost A0>

<vhost A>
  SSLProtocol +TLSV1.1
</vhost A>

<vhost B>
  SSLProtocol +TLSV1.2
</vhost B>

(If vhost A is both 1.1 and 1.2, then the extra phys-vhost isn't needed and 
the least-restrictive SSLProtocol should be stated first as the default
vhost.) This isn't too much different than the docs warning for 
https://httpd.apache.org/docs/2.4/mod/core.html#limitrequestfieldsize

However, because you've just changed the meaning of SSLProtocol in all
vhost cases, this doesn't appear to be a suitable change for an httpd 2.4.x 
revision. The patch invalidates apparently-working-today configurations
which were coded to the current phys-vhost semantics.

Right now, SSLProtocol is inherited global -> phys vhost. The behavioral
change causes this to be global -> virt vhost. That may require admins to
make major changes to all 'default' name based vhosts, and seems like an 
unwise side-effect of a configuration already carefully audited for
security considerations, for simply a subversion bump. Thoughts?
Comment 24 hasso.tepper 2019-03-09 10:24:44 UTC
There are a legit reasons why users would want to allow/disable TLS versions per virthost. I'm working for a hosting provider and I see all kind of crazy requirements – main reasons are . Some users want to disable TLS1.0 only (mainly PCI reasons), but ceratinly keep TLS1.1. Some users want to disable TLSv1.0 and TLSv1.1. Some users want to disable TLSv1.3 on top of other requirenments – missing support for Post-Handsahke Authentication in browsers seems to be a popular reason. ETC.

In short – it would be quite a task to make configure IP for every possible TLS combination and move virthosts (and DNS etc) accordingly.

The patch from Mike Haller works mostly and is OK with all restrictions (ie you can't enable protocols which are disabled by default in virthosts etc). But it doesn't work for TLSv1.3 any more and supporting it isn't trivial either AFAICS. What I'd like is to make this configuration work:

<vhost default>
  SSLProtocol all -SSLv3
</vhost default>

<vhost A>
  SSLProtocol all -SSLv3 -TLSv1.1
</vhost A>

<vhost B>
  SSLProtocol all -SSLv3 -TLSV1.3
</vhost B>

If I'd extend the patch with support for TLS 1.3, vhost B would be just unaccessible for all clients supporting TLS 1.3.
Comment 25 Dave Schneider 2019-03-29 19:16:16 UTC
(In reply to William A. Rowe Jr. from comment #23)
> Re comment #21, I think you are suggesting the correspondence of SNI and
> SSLCipherSuite? The client hello is processed by invoking the OpenSSL read 
> client hello which corresponds to the SSLProtocol defined by the first 
> (physical matching) IP:Port vhost. Once that happens I guess we can trust
> the version reported as you suggest, provided that processing all occurs
> prior to the server hello. If the SNI callback is being invoked before 
> the server hello, I believe your patch may be sufficient. (It is currently
> missing SSLV3 and TLSV1_3 logic, easy fixes.) I'd be concerned about future
> maintenance, but there are enough other places we can trip over TLSV1_4 that
> it probably isn't a fair objection.
> 
> I think we can document the somewhat crazy example Stefan was looking for;
> 
> <vhost A0>
>   SSLProtocol +TLSV1.1 +TLSV1.2
> </vhost A0>
> 
> <vhost A>
>   SSLProtocol +TLSV1.1
> </vhost A>
> 
> <vhost B>
>   SSLProtocol +TLSV1.2
> </vhost B>
> 
> (If vhost A is both 1.1 and 1.2, then the extra phys-vhost isn't needed and 
> the least-restrictive SSLProtocol should be stated first as the default
> vhost.) This isn't too much different than the docs warning for 
> https://httpd.apache.org/docs/2.4/mod/core.html#limitrequestfieldsize
> 
> However, because you've just changed the meaning of SSLProtocol in all
> vhost cases, this doesn't appear to be a suitable change for an httpd 2.4.x 
> revision. The patch invalidates apparently-working-today configurations
> which were coded to the current phys-vhost semantics.
> 
> Right now, SSLProtocol is inherited global -> phys vhost. The behavioral
> change causes this to be global -> virt vhost. That may require admins to
> make major changes to all 'default' name based vhosts, and seems like an 
> unwise side-effect of a configuration already carefully audited for
> security considerations, for simply a subversion bump. Thoughts?

Not knowing the mod_ssl internals, I suppose this may make sense from that standpoint, but going from the SSLProtocol documentation that's certainly not clear.  The context for SSLProtocol is listed as "server config" and "virtual host", with no mention that only IP virtual hosts support this.  The behavior described here would imply that SSLProtocol is currently ignored for all name virtual hosts except the first one (default), even when the SNI extension is passed. But why would that be more logical than treating it as applicable for for any virtual host that specifies it, regardless of whether it was IP or name based?

The spec on the SNI extension isn't real specific, focusing mostly on presenting the right server certificate, but it does say the following:

   A server that receives a client hello containing the "server_name"
   extension MAY use the information contained in the extension to guide
   its selection of an appropriate certificate to return to the client,
   and/or other aspects of security policy.

So it doesn't mandate that "other aspects of security policy" be determined using the SNI value, but I would argue that makes as much sense as guiding the selection of the certificate, which is already being done.