从启用 HTTP2 导致网站无法访问说起

从启用 HTTP2 导致网站无法访问说起,第1张

从启用HTTP/2导致网站无法访问说起

文/杰瑞曲

最近有几个朋友遇到了打开HTTP/2后无法访问网站的问题。有些网站只是火狐无法访问,请求可以中止;通过控制台网络面板;有些网站Firefox无法访问,甚至Chrome也会跳转到错误页面,错误代码为“err_spdy_impulsive_transport_security”。奇怪的是,只要去掉对HTTP/2的支持(比如去掉Nginxlisten配置中的http2),就万事大吉了。也就是说,无法访问的现象只存在于HTTPS+HTTP/2的组合中,单独提供HTTPS服务就不错了。

这个问题有意思。本文不仅会告诉你如何解决,还会帮你找出问题的前因后果。如果你只关心结论,就看最后的总结。

首先,网站无法访问的可能性有很多。一般来说,你应该从基本项目开始检查:

  • 网络是否被屏蔽(可以查看是否能访问imququ.com_);
  • 网站DNS解析是否正常(可通过ping、nslookup、dig等工具检查);
  • 是否可以建立TCP连接(可以通过telnet检查,比如Telnetimququ.com443);
  • 如果可以建立TCP连接,则至少Web服务器正在运行,并且Web服务器的本地网络正常。如果仍有问题,我们应该开始排除应用层故障,例如:

  • 域名是否因为未备案而被屏蔽(可以尝试使用IP,或者通过非标准端口访问);
  • Web程序是否太慢而无法返回响应(可以看到请求状态总是待定);通过浏览器的web面板);
  • 是否有响应,但内容是空(根据响应状态码检查服务器配置或业务码);
  • 对于HTTPS网站,在HTTP和TCP之间还有一层TLS。浏览器在发送HTTP消息之前,要和服务器建立TLS连接,非常复杂,容易出问题。例如:

  • 没有合法证书(过期、域名不匹配等。),一般浏览器都会给出明确的提示。特别是需要调查客户端在部署多个有IP的HTTPS站点时不支持SNI导致的非法证书);
  • 使用了浏览器不支持的证书类型(比如没有XPSP3补丁的IE6不支持SHA-2证书);
  • 使用浏览器不支持的TLS协议版本(比如IE6只支持SSLv2和SSLv3);默认);
  • 使用了浏览器不支持的CipherSuite(例如,只有Chrome支持ECDhe-ECDsa-chacha20-poly1305);
  • 关于部署HTTPS时的一些注意事项,可以参考我之前的文章《关于启用HTTPS的一些经验分享(二)》,这不是本文的重点,就不讨论了。

    总之,前面列出的这么多可能性,基本都和本文要解决的问题无关!如果响应被延迟,或者证书是非法的,就不可能访问它。没有理由不启用HTTP/2。

    其实Chrome的错误代码“err_spdy_impulsive_transport_security”已经给出了两个提示:

  • 它与HTTP/2有关。SPDY是HTTP/2的前身,这个错误码应该是从SPDY时代继承来的;
  • 它关系到TLS安全性。对于存在安全风险的HTTPS网站,现代浏览器会阻止TLS握手成功。比如最新的Chrome48会拒绝与“以RC4为对称加密算法的CipherSuite”建立TLS连接;
  • 从Wireshark的数据包捕获中可以看到,在这种情况下,浏览器在TLS握手过程中发送了“加密警报”,然后主动断开TCP。TLS连接未成功建立,因此无法访问该页面。

    之前看HTTP/2RFC的时候,了解到HTTP/2协议中对TLS有更严格的限制:比如HTTP/2中只能使用TLSv1.2+,禁用了上百个CipherSuite(详见:TLS1.2CipherSuite黑名单)。此时,您可以确定该错误的原因是服务器上没有启用TLSv1.2,或者CipherSuite的配置有问题。在这种情况下,服务器支持TLSv1.2,只有后者可能会有问题。

    CipherSuite,即加密套件,在整个TLS协议中至关重要。

    建立TLS连接时,浏览器需要在客户端Hello握手中提供其支持的CipherSuite列表和应用协议列表(由TLSALPN扩展),服务器通过服务器Hello握手返回选择的CipherSuite和应用协议。如果服务器选择的应用协议是HTTP/2,浏览器需要检查CipherSuite是否在HTTP/2的黑名单中,如果存在就终止TLS握手。

    当然,如果浏览器本身不支持HTTP/2,客户端Hello握手中的ALPN扩展就不会包含h2(实际上ALPN扩展可能不存在),服务器也不会选择HTTP/2作为后续的应用协议。其实这个过程就是HTTP/2协议协商机制。

    HTTP/2对CipherSuite有更严格的限制。用于传输HTTP/1.1加密流量的CipherSuite不能用于传输HTTP/2加密流量。这也导致HTTPS网站以前工作得很好。启用HTTP/2后,可能无法通过HTTP/2访问,因为CipherSuite被禁用。

    了解了原理之后,我们来看一个具体案例(注:此案例来源于本博客用户的评论,via):

    在Nginx中配置以下CipherSuite并启用HTTP/2,在最新的Firefox中无法访问:

    SSL_ciphersECDhe-RSA-chacha20-poly1305:ECDhe-RSA-AES256-GCM-SHA384:ECDhe-RSA-AES256-SHA384:ECDhe-RSA-AES256-SHA:ECDhe-RSA-AES128-GCM-SHA256:ECDhe-RSA-AES128-SHA256:ECDhe-RSA-AES128-SHA:AES256-GCM-SHA阿努尔:伊努尔!导出:!山茶花:!德斯:!MD5:!PSK:!RC4;

    注:以上配置中的CHACHA20/POLY1305是Google开发的。以前需要LibreSSL、BoringSSL或者CloudFlare的OpenSSL补丁来支持,最新版本的OpenSSL已经内置了对它的支持。

    我们来看看上面配置中指定的具体CipherSuite(注意:下面命令中的openssl版本是libresl2.3.1):

    OpenSSLciphers-V'ECDhe-RSA-chacha20-poly1305:ECDhe-RSA-AES256-GCM-SHA384:ECDhe-RSA-AES256-SHA:ECDhe-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDhe-RSA-AES128-SHA:AES256-SHA阿努尔:伊努尔!导出:!山茶花:!德斯:!MD5:!PSK:!RC4“|column-t”

    运行结果如下:

    0xCC,0x13-ECDHE-RSA-CHACHA20-POLY1305TLSv1.2Kx=ECDHAu=RSAEnc=ChaCha20-Poly1305
    Mac=AEAD 0xC0,0x30-ECDHE-RSA-AES256-GCM-SHA384TLSv1.2Kx=ECDHAu=RSAEnc=AESGCM(256)
    Mac=AEAD 0xC0,0x28-ECDHE-RSA-AES256-SHA384TLSv1.2Kx=ECDHAu=RSAEnc=AES(256)
    Mac=SHA384 0xC0,0x14-ECDHE-RSA-AES256-SHASSLv3Kx=ECDHAu=RSAEnc=AES(256)
    Mac=SHA1 0xC0,0x2F-ECDHE-RSA-AES128-GCM-SHA256TLSv1.2Kx=ECDHAu=RSAEnc=AESGCM(128)
    Mac=AEAD 0xC0,0x27-ECDHE-RSA-AES128-SHA256TLSv1.2Kx=ECDHAu=RSAEnc=AES(128)
    Mac=SHA256 0xC0,0x13-ECDHE-RSA-AES128-SHASSLv3Kx=ECDHAu=RSAEnc=AES(128)
    Mac=SHA1 0x00,0x9D-AES256-GCM-SHA384TLSv1.2Kx=RSAAu=RSAEnc=AESGCM(256)
    Mac=AEAD 0x00,0x3D-AES256-SHA256TLSv1.2Kx=RSAAu=RSAEnc=AES(256)
    Mac=SHA256 0x00,0x35-AES256-SHASSLv3Kx=RSAAu=RSAEnc=AES(256)
    Mac=SHA1 0x00,0x9C-AES128-GCM-SHA256TLSv1.2Kx=RSAAu=RSAEnc=AESGCM(128)
    Mac=AEAD 0x00,0x3C-AES128-SHA256TLSv1.2Kx=RSAAu=RSAEnc=AES(128)
    Mac=SHA256 0x00,0x2F-AES128-SHASSLv3Kx=RSAAu=RSAEnc=AES(128)
    Mac=SHA1 0xC0,0x12-ECDHE-RSA-DES-CBC3-SHASSLv3Kx=ECDHAu=RSAEnc=3DES(168)
    Mac=SHA1 0x00,0x0A-DES-CBC3-SHASSLv3Kx=RSAAu=RSAEnc=3DES(168)
    Mac=SHA1

    然后通过Wireshark获取客户端Hello中Firefox发送的CipherSuite列表,如下:

    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC0,0x2B) TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC0,0x2F) TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xC0,0x0A) TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xC0,0x09) TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC0,0x13) TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC0,0x14) TLS_DHE_RSA_WITH_AES_128_CBC_SHA(0x00,0x33) TLS_DHE_RSA_WITH_AES_256_CBC_SHA(0x00,0x39) TLS_RSA_WITH_AES_128_CBC_SHA(0x00,0x2F) TLS_RSA_WITH_AES_256_CBC_SHA(0x00,0x35) TLS_RSA_WITH_3DES_EDE_CBC_SHA(0x00,0x0A)

    CipherSuite协商的目的是找出两端支持的Suite,也就是取出两者的交集:

    TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC0,0x2F) TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC0,0x13) TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC0,0x14) TLS_RSA_WITH_AES_128_CBC_SHA(0x00,0x2F) TLS_RSA_WITH_AES_256_CBC_SHA(0x00,0x35) TLS_RSA_WITH_3DES_EDE_CBC_SHA(0x00,0x0A)

    乍一看,选择还挺多的,但是别忘了,HTTP/2协议中也禁止了上百种。移除此零件后,仅:

    TLS_ECDhe_RSA_WITH_AES_128_GCM_sha256(0xc0,0x2F)

    奇怪的是,至少有一个满足所有条件的套件。为什么还是握手失败?通过Wireshark查看ServerHello,你会发现在这种情况下,通过Firefox访问,服务器选择的包是0xC0,0x14,而不是0xC0,0x2F。

    Nginx有一个SSL_prefer_server_cipher配置。如果设置为on,表示协商CipherSuite时,计算交集后,按照服务器配置的Suite列表顺序返回第一个,可以提高安全性。在ssl_ciphers配置中,0xc0和0x14位于0xc0和0x2f之前。当ssl_prefer_server_ciphers打开时,将选择被HTTP/2禁用的0xc0和0x14,导致最终的HTTPS+HTTP/2握手失败。

    那为什么这个配置在Chrome里很正常?Chrome支持的CipherSuite如下,大家可以自己分析一下。

    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC0,0x2B) TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC0,0x2F) TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(0x00,0x9E) TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(0xCC,0x14) TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xCC,0x13) TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xC0,0x0A) TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC0,0x14) TLS_DHE_RSA_WITH_AES_256_CBC_SHA(0x00,0x39) TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xC0,0x09) TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC0,0x13) TLS_DHE_RSA_WITH_AES_128_CBC_SHA(0x00,0x33) TLS_RSA_WITH_AES_128_GCM_SHA256(0x00,0x9C) TLS_RSA_WITH_AES_256_CBC_SHA(0x00,0x35) TLS_RSA_WITH_AES_128_CBC_SHA(0x00,0x2F) TLS_RSA_WITH_3DES_EDE_CBC_SHA(0x00,0x0A)

    根据这个案例,把Nginx配置中的0xc0,0x2f(ECDHE-RSA-AES128-GCM-SHA256)移到0xC0,0x14(ECDHE-RSA-AES256-SHA)可以解决最新Firefox下无法访问的问题。当然,正如我在之前的文章中多次强调的,配置TLS时一定要参考权威文档,比如Mozilla的推荐配置和CloudFlare使用的配置。经过测试,使用这两种配置的HTTPS站点在启用HTTP/2后没有问题。

    简单总结一下,如果HTTPS网站启用了HTTP/2可以正常工作,请检查服务器的这两个配置:

    1)是否TLSv1.2已启用;

    2)密码套件的配置是否正确。

    本文写到这里。您通常会遇到关于HTTP(S)和HTTP/2的问题。请给我留言或发邮件讨论。

    欢迎分享,转载请注明来源:内存溢出

    原文地址: http://outofmemory.cn/zz/764667.html

    (0)
    打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
    上一篇 2022-05-01
    下一篇 2022-05-02

    发表评论

    登录后才能评论

    评论列表(0条)

    保存