15

我在使用 CURL 通过 SSL 请求 URI 的 PHP 组件中遇到以下错误:

cURL error 35: gnutls_handshake() failed: A TLS packet with unexpected length was received.

此错误发生在 travis-ci.org 环境中,但不会发生在我们的任何测试环境中。请参阅 travis-ci 构建144663700

我发现在 Travis 工作程序中运行的 PHP 版本在“Ubuntu 12.04.5 LTS”上再次编译“GnuTLS/2.12.14”或在“Ubuntu 14.04.3 LTS”上使用“GnuTLS/2.12.23”编译。

在我们的开发环境中,我们使用针对 Debian(各种版本)上的“OpenSSL/1.0.1t”编译的标准包。

因此,我认为问题与“GnuTLS/2.12.14”或“GnuTLS/2.12.23”或编译它们的参数有关。

我尝试使用 CURL 常量 CURLOPT_SSLVERSION 限制 SSL 版本,但这并不能解决问题。

根据 www.ssllabs.com,有问题的主机 - api.reporting.cloud - 支持 TLS 1.2、TLS 1.1 和 TLS 1.0。

有人对我有任何提示或指示吗?

4

3 回答 3

8

解决此问题的方法是将 travis-ci 配置为使用标准的 Ubuntu Trusty php5-cli 和 php5-curl 包。标准包提供 CURL_SSLVERSION_TLSv1_1 常量。

.travis.yml 文件如下所示:

sudo: required

dist: trusty

language: php

before_install:
  - sudo apt-get -y install git zip php5-cli php5-curl

before_script:
  - php -r "printf('PHP %s', phpversion());"
  - composer self-update
  - composer install --no-interaction

script:
  - mkdir -p ./build/logs
  - ./vendor/bin/phpunit

在 PHP 源代码中,只需在 travis-ci 执行 PHP 代码的情况下设置上述常量即可:

if (getenv('TRAVIS')) {
    $options['curl'][CURLOPT_SSLVERSION] = CURL_SSLVERSION_TLSv1_1;
}

此解决方法的缺点是它仅适用于 Ubuntu Trusty 提供的特定 PHP 版本 (PHP 5.5)。考虑到 PHP 5.5 已于 2016 年 7 月 10 日结束生命周期,此解决方案是不可接受的。

travis-ci 更新到 Ubuntu 16.04 LTS 将是理想的选择,但 travis-ci 的基础设施经理 Brandon Burton 在2016 年 2 月 28 日写道:

鉴于此,我们目前专注于支持 12.04 和 14.04 作为我们的主要环境。目前,我们今年不太可能支持 16.04 作为原生环境。

因此,我们似乎在一段时间内被 Ubuntu Trusty 困住了。

这个问题的根源是在 travis-ci 上运行的 PHP 版本是从 2011 年开始使用 gnutls-cli (GnuTLS) 2.12.23 编译的。这个特定版本的 gnutls-cli 在某些(但不是全部)TLS 1.2 上存在问题连接。

@travis-ci:是否可以针对更现代的 GnuTLS 版本重新编译您使用的 PHP 版本 - 或者至少是更好地支持 TLS 1.2 的版本?

于 2016-07-18T06:08:54.870 回答
4

在 PHP 中,可以通过 CURL_SSLVERSION_* 常量控制 curl 使用的 SSL 协议。

通过设置:

curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_1);

我可以强制 curl 使用“TLS 1.1”。

通过设置:

curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);

我可以强制 curl 使用“TLS 1.0”。

为了测试所有可能的 SSL 协议,我创建了以下脚本,然后由 travis-ci 执行:

<?php

$sslVersions = [
    CURL_SSLVERSION_DEFAULT,
    CURL_SSLVERSION_TLSv1,
    CURL_SSLVERSION_TLSv1_0,
    CURL_SSLVERSION_TLSv1_1,
    CURL_SSLVERSION_TLSv1_2,
    CURL_SSLVERSION_SSLv2,
    CURL_SSLVERSION_SSLv3,
];

var_dump(curl_version());

foreach ($sslVersions as $sslVersion) {

    $uri = "https://api.reporting.cloud";

    printf("Trying %d", $sslVersion);
    echo PHP_EOL;

    $ch = curl_init($uri);

    curl_setopt($ch, CURLOPT_VERBOSE        , true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER , 1);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT , 0);
    curl_setopt($ch, CURLOPT_TIMEOUT        , 2);
    curl_setopt($ch, CURLOPT_SSLVERSION     , $sslVersion);

    if (curl_exec($ch) === false) {
        var_dump(curl_error($ch));
    } else {
        curl_close($ch);
    }

    echo PHP_EOL;
    echo PHP_EOL;

}

exit(1);

这个脚本在我的开发环境中的输出是:

array(9) {
  ["version_number"]=>
  int(468480)
  ["age"]=>
  int(3)
  ["features"]=>
  int(182173)
  ["ssl_version_number"]=>
  int(0)
  ["version"]=>
  string(6) "7.38.0"
  ["host"]=>
  string(19) "x86_64-pc-linux-gnu"
  ["ssl_version"]=>
  string(14) "OpenSSL/1.0.1t"
  ["libz_version"]=>
  string(5) "1.2.8"
  ["protocols"]=>
  array(21) {
    [0]=>
    string(4) "dict"
    [1]=>
    string(4) "file"
    [2]=>
    string(3) "ftp"
    [3]=>
    string(4) "ftps"
    [4]=>
    string(6) "gopher"
    [5]=>
    string(4) "http"
    [6]=>
    string(5) "https"
    [7]=>
    string(4) "imap"
    [8]=>
    string(5) "imaps"
    [9]=>
    string(4) "ldap"
    [10]=>
    string(5) "ldaps"
    [11]=>
    string(4) "pop3"
    [12]=>
    string(5) "pop3s"
    [13]=>
    string(4) "rtmp"
    [14]=>
    string(4) "rtsp"
    [15]=>
    string(3) "scp"
    [16]=>
    string(4) "sftp"
    [17]=>
    string(4) "smtp"
    [18]=>
    string(5) "smtps"
    [19]=>
    string(6) "telnet"
    [20]=>
    string(4) "tftp"
  }
}
Trying 0
* Rebuilt URL to: https://api.reporting.cloud/
* Hostname was NOT found in DNS cache
*   Trying 40.76.93.116...
* Connected to api.reporting.cloud (40.76.93.116) port 443 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-SHA384
* Server certificate:
*    subject: serialNumber=HRB 25927; 1.3.6.1.4.1.311.60.2.1.3=DE; businessCategory=Private Organization; C=DE; postalCode=28215; ST=Bremen; L=Bremen; street=Admiralstr. 54; O=Text Control GmbH; OU=ReportingCloud; OU=COMODO EV SSL; CN=api.reporting.cloud
*    start date: 2016-06-17 00:00:00 GMT
*    expire date: 2017-06-17 23:59:59 GMT
*    subjectAltName: api.reporting.cloud matched
*    issuer: C=GB; ST=Greater Manchester; L=Salford; O=COMODO CA Limited; CN=COMODO RSA Extended Validation Secure Server CA
*    SSL certificate verify ok.
> GET / HTTP/1.1
Host: api.reporting.cloud
Accept: */*

< HTTP/1.1 200 OK
< Cache-Control: private
< Content-Type: text/html; charset=utf-8
* Server Microsoft-IIS/8.5 is not blacklisted
< Server: Microsoft-IIS/8.5
< X-AspNetMvc-Version: 5.2
< X-AspNet-Version: 4.0.30319
< X-Powered-By: ASP.NET
< Date: Fri, 15 Jul 2016 14:22:40 GMT
< Content-Length: 952
< 
* Connection #0 to host api.reporting.cloud left intact


Trying 1
* Rebuilt URL to: https://api.reporting.cloud/
* Hostname was found in DNS cache
*   Trying 40.76.93.116...
* Connected to api.reporting.cloud (40.76.93.116) port 443 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-SHA384
* Server certificate:
*    subject: serialNumber=HRB 25927; 1.3.6.1.4.1.311.60.2.1.3=DE; businessCategory=Private Organization; C=DE; postalCode=28215; ST=Bremen; L=Bremen; street=Admiralstr. 54; O=Text Control GmbH; OU=ReportingCloud; OU=COMODO EV SSL; CN=api.reporting.cloud
*    start date: 2016-06-17 00:00:00 GMT
*    expire date: 2017-06-17 23:59:59 GMT
*    subjectAltName: api.reporting.cloud matched
*    issuer: C=GB; ST=Greater Manchester; L=Salford; O=COMODO CA Limited; CN=COMODO RSA Extended Validation Secure Server CA
*    SSL certificate verify ok.
> GET / HTTP/1.1
Host: api.reporting.cloud
Accept: */*

< HTTP/1.1 200 OK
< Cache-Control: private
< Content-Type: text/html; charset=utf-8
* Server Microsoft-IIS/8.5 is not blacklisted
< Server: Microsoft-IIS/8.5
< X-AspNetMvc-Version: 5.2
< X-AspNet-Version: 4.0.30319
< X-Powered-By: ASP.NET
< Date: Fri, 15 Jul 2016 14:22:40 GMT
< Content-Length: 952
< 
* Connection #0 to host api.reporting.cloud left intact


Trying 4
* Rebuilt URL to: https://api.reporting.cloud/
* Hostname was found in DNS cache
*   Trying 40.76.93.116...
* Connected to api.reporting.cloud (40.76.93.116) port 443 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSL connection using TLSv1.0 / ECDHE-RSA-AES256-SHA
* Server certificate:
*    subject: serialNumber=HRB 25927; 1.3.6.1.4.1.311.60.2.1.3=DE; businessCategory=Private Organization; C=DE; postalCode=28215; ST=Bremen; L=Bremen; street=Admiralstr. 54; O=Text Control GmbH; OU=ReportingCloud; OU=COMODO EV SSL; CN=api.reporting.cloud
*    start date: 2016-06-17 00:00:00 GMT
*    expire date: 2017-06-17 23:59:59 GMT
*    subjectAltName: api.reporting.cloud matched
*    issuer: C=GB; ST=Greater Manchester; L=Salford; O=COMODO CA Limited; CN=COMODO RSA Extended Validation Secure Server CA
*    SSL certificate verify ok.
> GET / HTTP/1.1
Host: api.reporting.cloud
Accept: */*

< HTTP/1.1 200 OK
< Cache-Control: private
< Content-Type: text/html; charset=utf-8
* Server Microsoft-IIS/8.5 is not blacklisted
< Server: Microsoft-IIS/8.5
< X-AspNetMvc-Version: 5.2
< X-AspNet-Version: 4.0.30319
< X-Powered-By: ASP.NET
< Date: Fri, 15 Jul 2016 14:22:40 GMT
< Content-Length: 952
< 
* Connection #0 to host api.reporting.cloud left intact


Trying 5
* Rebuilt URL to: https://api.reporting.cloud/
* Hostname was found in DNS cache
*   Trying 40.76.93.116...
* Connected to api.reporting.cloud (40.76.93.116) port 443 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSL connection using TLSv1.1 / ECDHE-RSA-AES256-SHA
* Server certificate:
*    subject: serialNumber=HRB 25927; 1.3.6.1.4.1.311.60.2.1.3=DE; businessCategory=Private Organization; C=DE; postalCode=28215; ST=Bremen; L=Bremen; street=Admiralstr. 54; O=Text Control GmbH; OU=ReportingCloud; OU=COMODO EV SSL; CN=api.reporting.cloud
*    start date: 2016-06-17 00:00:00 GMT
*    expire date: 2017-06-17 23:59:59 GMT
*    subjectAltName: api.reporting.cloud matched
*    issuer: C=GB; ST=Greater Manchester; L=Salford; O=COMODO CA Limited; CN=COMODO RSA Extended Validation Secure Server CA
*    SSL certificate verify ok.
> GET / HTTP/1.1
Host: api.reporting.cloud
Accept: */*

< HTTP/1.1 200 OK
< Cache-Control: private
< Content-Type: text/html; charset=utf-8
* Server Microsoft-IIS/8.5 is not blacklisted
< Server: Microsoft-IIS/8.5
< X-AspNetMvc-Version: 5.2
< X-AspNet-Version: 4.0.30319
< X-Powered-By: ASP.NET
< Date: Fri, 15 Jul 2016 14:22:41 GMT
< Content-Length: 952
< 
* Connection #0 to host api.reporting.cloud left intact


Trying 6
* Rebuilt URL to: https://api.reporting.cloud/
* Hostname was found in DNS cache
*   Trying 40.76.93.116...
* Connected to api.reporting.cloud (40.76.93.116) port 443 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-SHA384
* Server certificate:
*    subject: serialNumber=HRB 25927; 1.3.6.1.4.1.311.60.2.1.3=DE; businessCategory=Private Organization; C=DE; postalCode=28215; ST=Bremen; L=Bremen; street=Admiralstr. 54; O=Text Control GmbH; OU=ReportingCloud; OU=COMODO EV SSL; CN=api.reporting.cloud
*    start date: 2016-06-17 00:00:00 GMT
*    expire date: 2017-06-17 23:59:59 GMT
*    subjectAltName: api.reporting.cloud matched
*    issuer: C=GB; ST=Greater Manchester; L=Salford; O=COMODO CA Limited; CN=COMODO RSA Extended Validation Secure Server CA
*    SSL certificate verify ok.
> GET / HTTP/1.1
Host: api.reporting.cloud
Accept: */*

< HTTP/1.1 200 OK
< Cache-Control: private
< Content-Type: text/html; charset=utf-8
* Server Microsoft-IIS/8.5 is not blacklisted
< Server: Microsoft-IIS/8.5
< X-AspNetMvc-Version: 5.2
< X-AspNet-Version: 4.0.30319
< X-Powered-By: ASP.NET
< Date: Fri, 15 Jul 2016 14:22:41 GMT
< Content-Length: 952
< 
* Connection #0 to host api.reporting.cloud left intact


Trying 2
* Rebuilt URL to: https://api.reporting.cloud/
* Hostname was found in DNS cache
*   Trying 40.76.93.116...
* Connected to api.reporting.cloud (40.76.93.116) port 443 (#0)
* OpenSSL was built without SSLv2 support
* Closing connection 0
string(39) "OpenSSL was built without SSLv2 support"


Trying 3
* Rebuilt URL to: https://api.reporting.cloud/
* Hostname was found in DNS cache
*   Trying 40.76.93.116...
* Connected to api.reporting.cloud (40.76.93.116) port 443 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* Unknown SSL protocol error in connection to api.reporting.cloud:443 
* Closing connection 0
string(68) "Unknown SSL protocol error in connection to api.reporting.cloud:443 "

在这里,我们可以清楚地看到“使用 TLSv1.0 的 SSL 连接”正在正确连接到后端服务器。

但是,在 travi-ci 上运行相同的脚本会导致以下结果:

PHP Notice:  Use of undefined constant CURL_SSLVERSION_TLSv1_0 - assumed 'CURL_SSLVERSION_TLSv1_0' in /home/travis/build/TextControl/txtextcontrol-reportingcloud-php/demo/ssl-issue.php on line 7
PHP Stack trace:
PHP   1. {main}() /home/travis/build/TextControl/txtextcontrol-reportingcloud-php/demo/ssl-issue.php:0

Notice: Use of undefined constant CURL_SSLVERSION_TLSv1_0 - assumed 'CURL_SSLVERSION_TLSv1_0' in /home/travis/build/TextControl/txtextcontrol-reportingcloud-php/demo/ssl-issue.php on line 7

Call Stack:
    0.0002     241400   1. {main}() /home/travis/build/TextControl/txtextcontrol-reportingcloud-php/demo/ssl-issue.php:0

PHP Notice:  Use of undefined constant CURL_SSLVERSION_TLSv1_1 - assumed 'CURL_SSLVERSION_TLSv1_1' in /home/travis/build/TextControl/txtextcontrol-reportingcloud-php/demo/ssl-issue.php on line 8
PHP Stack trace:
PHP   1. {main}() /home/travis/build/TextControl/txtextcontrol-reportingcloud-php/demo/ssl-issue.php:0

Notice: Use of undefined constant CURL_SSLVERSION_TLSv1_1 - assumed 'CURL_SSLVERSION_TLSv1_1' in /home/travis/build/TextControl/txtextcontrol-reportingcloud-php/demo/ssl-issue.php on line 8

Call Stack:
    0.0002     241400   1. {main}() /home/travis/build/TextControl/txtextcontrol-reportingcloud-php/demo/ssl-issue.php:0

PHP Notice:  Use of undefined constant CURL_SSLVERSION_TLSv1_2 - assumed 'CURL_SSLVERSION_TLSv1_2' in /home/travis/build/TextControl/txtextcontrol-reportingcloud-php/demo/ssl-issue.php on line 9
PHP Stack trace:
PHP   1. {main}() /home/travis/build/TextControl/txtextcontrol-reportingcloud-php/demo/ssl-issue.php:0

Notice: Use of undefined constant CURL_SSLVERSION_TLSv1_2 - assumed 'CURL_SSLVERSION_TLSv1_2' in /home/travis/build/TextControl/txtextcontrol-reportingcloud-php/demo/ssl-issue.php on line 9

Call Stack:
    0.0002     241400   1. {main}() /home/travis/build/TextControl/txtextcontrol-reportingcloud-php/demo/ssl-issue.php:0

array(9) {
  'version_number' =>
  int(464384)
  'age' =>
  int(3)
  'features' =>
  int(50749)
  'ssl_version_number' =>
  int(0)
  'version' =>
  string(6) "7.22.0"
  'host' =>
  string(19) "x86_64-pc-linux-gnu"
  'ssl_version' =>
  string(14) "GnuTLS/2.12.14"
  'libz_version' =>
  string(7) "1.2.3.4"
  'protocols' =>
  array(18) {
    [0] =>
    string(4) "dict"
    [1] =>
    string(4) "file"
    [2] =>
    string(3) "ftp"
    [3] =>
    string(4) "ftps"
    [4] =>
    string(6) "gopher"
    [5] =>
    string(4) "http"
    [6] =>
    string(5) "https"
    [7] =>
    string(4) "imap"
    [8] =>
    string(5) "imaps"
    [9] =>
    string(4) "ldap"
    [10] =>
    string(4) "pop3"
    [11] =>
    string(5) "pop3s"
    [12] =>
    string(4) "rtmp"
    [13] =>
    string(4) "rtsp"
    [14] =>
    string(4) "smtp"
    [15] =>
    string(5) "smtps"
    [16] =>
    string(6) "telnet"
    [17] =>
    string(4) "tftp"
  }
}
Trying 0
* About to connect() to api.reporting.cloud port 443 (#0)
*   Trying 40.76.93.116... * connected
* found 164 certificates in /etc/ssl/certs/ca-certificates.crt
* gnutls_handshake() failed: A TLS packet with unexpected length was received.
* Closing connection #0
string(76) "gnutls_handshake() failed: A TLS packet with unexpected length was received."


Trying 1
* About to connect() to api.reporting.cloud port 443 (#0)
*   Trying 40.76.93.116... * connected
* found 164 certificates in /etc/ssl/certs/ca-certificates.crt
* gnutls_handshake() failed: A TLS packet with unexpected length was received.
* Closing connection #0
string(76) "gnutls_handshake() failed: A TLS packet with unexpected length was received."


Trying 0
* About to connect() to api.reporting.cloud port 443 (#0)
*   Trying 40.76.93.116... * connected
* found 164 certificates in /etc/ssl/certs/ca-certificates.crt
* gnutls_handshake() failed: A TLS packet with unexpected length was received.
* Closing connection #0
string(76) "gnutls_handshake() failed: A TLS packet with unexpected length was received."


Trying 0
* About to connect() to api.reporting.cloud port 443 (#0)
*   Trying 40.76.93.116... * connected
* found 164 certificates in /etc/ssl/certs/ca-certificates.crt
* gnutls_handshake() failed: A TLS packet with unexpected length was received.
* Closing connection #0
string(76) "gnutls_handshake() failed: A TLS packet with unexpected length was received."


Trying 0
* About to connect() to api.reporting.cloud port 443 (#0)
*   Trying 40.76.93.116... * connected
* found 164 certificates in /etc/ssl/certs/ca-certificates.crt
* gnutls_handshake() failed: A TLS packet with unexpected length was received.
* Closing connection #0
string(76) "gnutls_handshake() failed: A TLS packet with unexpected length was received."


Trying 2
* About to connect() to api.reporting.cloud port 443 (#0)
*   Trying 40.76.93.116... * connected
* GnuTLS does not support SSLv2
* Closing connection #0
string(29) "GnuTLS does not support SSLv2"


Trying 3
* About to connect() to api.reporting.cloud port 443 (#0)
*   Trying 40.76.93.116... * connected
* found 164 certificates in /etc/ssl/certs/ca-certificates.crt
* gnutls_handshake() failed: A TLS packet with unexpected length was received.
* Closing connection #0
string(76) "gnutls_handshake() failed: A TLS packet with unexpected length was received."

我还注意到常量 CURL_SSLVERSION_TLSv1_0、CURL_SSLVERSION_TLSv1_1 和 CURL_SSLVERSION_TLSv1_2 在 travis-ci 的 PHP 5.6 和 PHP 7 版本上不可用。

总而言之,我已经遍历了所有可能的 CURL_SSLVERSION_* 常量,并且没有一个允许我连接到 travis-ci 上的 api.reporting.cloud,无论我使用哪个 PHP 版本。

有人对我如何从 travis-ci 连接到 api.reporting.cloud 有任何建议吗?

于 2016-07-15T14:44:37.793 回答
2

我在这个邮件列表中找到了问题的解决方案:

服务器不喜欢 gnutls 2.12 的 TLS 1.2 支持中的某些内容,因为如果禁用它,它似乎可以工作。相同的服务器与 gnutls 3.2 一起工作,两个版本的客户端 hello 的唯一区别是 gnutls 3.2 启用了更多功能。

我正在使用(必须使用)“gnutls-cli (GnuTLS) 2.12.23”。

以下返回上述错误:

gnutls-cli --priority "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.2" api.reporting.cloud

然而,强制“TLS 1.1”或“TLS 1.0”,按预期返回:

gnutls-cli --priority "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.1" api.reporting.cloud
gnutls-cli --priority "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.0" api.reporting.cloud

下一步是通过 CURL 从 PHP 进行此设置(在库版本错误的特定情况下)。

于 2016-07-15T10:25:14.863 回答