【问题标题】:How to read TLS certificates websockets using PHP?如何使用 PHP 读取 TLS 证书 websockets?
【发布时间】:2018-10-21 15:24:53
【问题描述】:

我正在尝试连接到由 PHP 创建的安全 websocket,但由于某种原因它不起作用。证书文件对于 PHP 是可读的。

这是我目前的代码(PHP 方面;为简单起见,精简了代码):

$context = stream_context_create();
stream_context_set_option($context, 'ssl', 'allow_self_signed', false);
stream_context_set_option($context, 'ssl', 'verify_peer', true);
stream_context_set_option($context,  'ssl', 'peer_name', 'example.com');
stream_context_set_option($context,  'ssl', 'CN_match', 'example.com');
stream_context_set_option($context,  'ssl', 'SNI_enabled', true);
stream_context_set_option($context, 'ssl', 'local_cert',  '/path/to/ssl/cert/example.com');
stream_context_set_option($context, 'ssl', 'local_pk', '/path/to/ssl/private/example.com');

$serverSocket = stream_socket_server(
        'tls://example.com:8090',
        $errNo,
        $errStr,
        \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
        $context
);
$client = stream_socket_accept($serverSocket);

// send initial websocket connection stuff
$request = socket_read($client, 5000);
preg_match('#Sec-WebSocket-Key: (.*)\r\n#', $request, $matches);
$key = base64_encode(pack(
    'H*',
    sha1($matches[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
));
$headers = "HTTP/1.1 101 Switching Protocols\r\n";
$headers .= "Upgrade: websocket\r\n";
$headers .= "Connection: Upgrade\r\n";
$headers .= 'Sec-WebSocket-Version: 13' . "\r\n";
$headers .= 'Sec-WebSocket-Accept: ' . $key . "\r\n\r\n";
socket_write($client, $headers, \mb_strlen($headers));

// do something here...

socket_close($client);
socket_close($serverSocket);

客户端:

var con = new WebSocket('wss://' + host + ':' + port);
var $chat = $('#chat');

con.onmessage = function(e) {
    $chat.append('<p>' + e.data + '</p>');
};

con.onopen = function(e) {
    con.send('Hello Me!');
};

con.onclose = function (e) {
    console.log('connection closed.', arguments);
}

我没有任何 *.pem 文件。只是在 apache web 服务器中使用的两个文件。如果需要,可以将该文件转换为 pem 文件。但我认为它也应该在 php 中使用这两个文件,不是吗?

为了更好的测试,我们使用了一个带有 let's encrypt 证书的隔离子域。因为我们获得了对该服务器的完全访问权限。但是,从证书生成器中,我只得到了这两个提到的文件。对于 Web 服务器,它可以完美运行。但是如何为 php 中的 websocket 做同样的事情呢?

现在,使用此代码,在向客户端发送一些消息后,服务器脚本告诉我,它无法从证书中获取对等方。我不知道这条消息是什么意思以及如何解决这个问题。我也尝试过将local_certlocal_pk 相互交换,但无论如何都没有帮助。

编辑:经过一段时间的研究,事实证明,php 失败,每次组合都会出现另一个错误。

我生成的证书文件如下所示:

文件 /opt/psa/var/certificates/cert-0x8zHR:

-----BEGIN CERTIFICATE REQUEST-----
some letters
-----END CERTIFICATE REQUEST-----

-----BEGIN PRIVATE KEY-----
some letters
-----END PRIVATE KEY-----

-----BEGIN CERTIFICATE-----
some letters
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
some letters
-----END CERTIFICATE-----

文件 /opt/psa/var/certificates/cert-Du9H8N:

-----BEGIN CERTIFICATE-----
some letters
-----END CERTIFICATE-----

两者的证书字符串中的所有行都在字符位置 65 处结束。

正如您在 php 文档中看到的那样,php 期望得到一个 PEM 格式的文件:http://php.net/manual/en/context.ssl.php#context.ssl.local-cert

我发现本教程将我的两个文件转换为一个 PEM 文件,但我的证书看起来与网站上提到的不同,而且我不知道 php 需要在 PEM 文件中准确包含什么:https://www.digicert.com/ssl-support/pem-ssl-creation.htm

编辑 2: 如下所述,这是我得到的确切错误:

stream_context_set_option($cont, 'ssl',  'local_pk', '/opt/psa/var/certificates/cert-0x8zHR');
stream_context_set_option($cont, 'ssl', 'local_cert', '/opt/psa/var/certificates/cert-Du9H8N');

警告:stream_socket_accept():无法设置私钥文件 `/opt/psa/var/certificates/cert-0x8zHR' in ...在线...

警告:stream_socket_accept(): 无法在 ... on 中启用加密 线...

警告:stream_socket_accept():accept failed: Success in ... on line ...

警告:socket_read() 期望参数 1 是资源,布尔值 在线...中给出...

注意:未定义的偏移量:1 in ... on line ...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_close() 期望参数 1 是资源,布尔值 在线...中给出...

还有

stream_context_set_option($cont, 'ssl',  'local_cert', '/opt/psa/var/certificates/cert-0x8zHR');
stream_context_set_option($cont, 'ssl', 'local_pk', '/opt/psa/var/certificates/cert-Du9H8N');

警告:stream_socket_accept():无法设置私钥文件 `/opt/psa/var/certificates/cert-Du9H8N' in ...在线...

警告:stream_socket_accept(): 无法在 ... on 中启用加密 线...

警告:stream_socket_accept():accept failed: Success in ... on line ...

警告:socket_read() 期望参数 1 是资源,布尔值 在线...中给出...

注意:未定义的偏移量:1 in ... on line ...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_close() 期望参数 1 是资源,布尔值 在线...中给出...

并且与(只是 cert-0x8zHR 与 cert-Du9H8N 的串联)

$file = dirname(__FILE__, 3) . \DIRECTORY_SEPARATOR . 'fullchain.pem';
stream_context_set_option($cont, 'ssl', 'local_cert', $file);

警告:stream_socket_accept():无法在...中获取对等证书 ...

警告:stream_socket_accept():无法在 ... ...中启用加密

警告:stream_socket_accept():accept failed: Success in ... ...

警告:socket_read() 期望参数 1 是资源,布尔值 在线...中给出...

注意:未定义的偏移量:1 in ... on line ...

警告:socket_write() 期望参数 1 是资源,布尔值 在线...中给出...

警告:socket_write() 期望参数 1 是资源,布尔值 给... ...

警告:socket_write() 期望参数 1 是资源,布尔值 给... ...

警告:socket_write() 期望参数 1 是资源,布尔值 给... ...

警告:socket_write() 期望参数 1 是资源,布尔值 给... ...

警告:socket_write() 期望参数 1 是资源,布尔值 给... ...

警告:socket_close() 期望参数 1 是资源,布尔值 在线...中给出...

编辑 3: 是的,确实存在 openssl 错误。在第三个 socket_stream_accept 警告之后,我现在只使用 php doc 示例中的代码就得到了这个错误:

error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch

我在互联网上搜索了这个错误。他们说,如果出现这个错误,我选择了错误的证书文件。

另外,如果我播放这个命令:

openssl x509 -noout -in cert-0x8zHR -modulus

我得到了两个不同的模数字符串。但我不知道为什么,也不知道如何解决这个问题。这两个文件都在 apache web server vhost 配置中使用,它工作正常:

SSLEngine on
SSLVerifyClient none
SSLCertificateFile /opt/psa/var/certificates/cert-0x8zHR
SSLCACertificateFile /opt/psa/var/certificates/cert-Du9H8N

PS:如果你知道任何本地转换为 pem 文件的工具,请告诉我。在线转换是不可能的。

【问题讨论】:

  • 请您发布您遇到的确切错误吗?
  • @Rich 我刚刚用我尝试的不同组合添加了所有错误消息 :-)
  • “一些信件”对你的帮助不是很好……证书是公共数据,为什么不给呢?同样对于local_pk 属性,您正在向它传递一个名为cert-something 的文件,因此如果它确实是一个证书,那么将它作为密钥将永远不会起作用。确保向local_pk 提供您证书的相关私钥。

标签: php ssl websocket serversocket phpwebsocket


【解决方案1】:

你说:

我生成的证书文件如下所示:

文件 /opt/psa/var/certificates/cert-0x8zHR:

-----BEGIN CERTIFICATE REQUEST----- 一些字母 -----结束证书请求-----

-----开始私钥----- 一些字母 -----结束私钥-----

-----BEGIN CERTIFICATE-----一些字母 -----结束证书-----

-----BEGIN CERTIFICATE-----一些字母 -----结束证书-----

这在很多层面上都是错误的。

首先,一旦您获得证书,“证书请求”部分就完全没用了。你可以忽略这部分。

现在复制“PRIVATE KEY”部分,并将标题放在一个文件中。这是您的私钥,您可以在任何有“local_pk”选项或需要密钥的软件的地方使用。

那么你有两个证书。另一个文件中的另一个。您将需要对所有这些进行排序。如果您提供了真正的证书内容(这是公共内容),人们可能会更好地帮助您。这里只有“一些字母”我们只能猜测。

在上述文件中,两个证书可能是 CA 和中间证书。您应该将它们都复制到另一个文件中,这将对应于“ca_cert”选项。

我猜第二个文件是您的试用证书(只是再次猜测,没有您的详细信息)。在您拥有“local_cert”或要求证书的任何地方使用它。此证书应与私钥文件匹配(您无法手动验证,需要工具)。

一旦您创建了所有正确的文件,就不再使用第一个文件。我建议对文件名使用更好的语义,因为cert-Xcert-Y 将非常困难。请改用网站名称,以便您拥有www.example.com.certwww.example.com-ca.certwww.example.com.key 之类的文件,以便立即清楚什么是什么。或者使用一个名为www.example.com/ 的目录,然后在目录中添加cert.pemca.pemkey.pem。扩展程序本身不被软件使用,并且与内容无关,仅由您来定义有意义的事情。

所以首先对所有这些进行排序,以便您的问题更清楚。现在,您似乎在盲目地尝试一些东西,直到找到可行的东西,这在安全和 TLS 领域并不是理想的情况。

如果可能的话,我还鼓励您尝试使用更高级别的抽象库来处理 TLS,因为这显然太低了,暴露了太多选项,您会迷失方向。

【讨论】:

  • 您的解决方案均无效。正如我上面所说,这是一个让我们使用 plesk Web 界面的工具为该域生成的加密证书。因此,apache 配置行也被自动添加。但是,问题应该更多的是为什么它在 apache 中使用这两三行而不是在 php 中?
  • 有什么解决办法?目前尚不清楚您测试了什么以及您有什么错误。我只是在告诉如何使事情更正确,因为文件的内容与任何有意义的内容都不匹配。请注意,我对 Plesk 一无所知。您的第一个文件肯定不是证书,因为它是 CSR、私钥和 2 个证书的串联。出于安全原因,将私钥和证书放在同一个文件中很少是一个好主意。
  • 至于为什么它在一种情况下或多或少起作用,请参阅 Apache 文档:httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatefile 并注意这部分:“最后也可以将最终实体证书的私钥添加到证书文件中使用单独的 SSLCertificateKeyFile 指令。这种做法是非常不鼓励的。如果使用它,则必须在使用单独的密钥文件的证书之后配置使用这种嵌入密钥的证书文件。"。您应该改用SSLCertificateKeyFile,但前提是您修复了第一个文件内容。
  • 我尝试了您的解决方案,将证书划分为所需的部分。所以,我把私钥放在一个单独的文件中,我只使用了两个 -- BEGIN CERTIFICATE --- 块。这不起作用。 “必须在使用单独的密钥文件的证书之后配置使用这种嵌入式密钥的证书文件”是什么意思?
  • “这不起作用”很难排除故障。至于其余部分,它是 Apache 文档的直接引用。在所有情况下,即使在 Apache 配置中也不建议在同一个文件中混合私钥和证书,所以我认为这样做没有好处(只有缺点)。但这可能会受到 Plesk 的限制。无论您的 Apache 配置发生什么您可能控制或无法控制的情况,您都可以单独创建具有适当内容的新文件,并在您的 PHP 应用程序中使用它们。这应该让事情更清楚。另请参阅我之前的评论:这个 TLS 库似乎太低级了
【解决方案2】:

试试这个:

stream_context_set_option($cont, 'ssl', 'local_cert', '/opt/psa/var/certificates/cert-0x8zHR');
stream_context_set_option($cont, 'ssl', 'ca_file', '/opt/psa/var/certificates/cert-Du9H8N');

如果这不起作用,请尝试注释掉第二行 (ca_flie) 并将 verify_peer 设置为 false

两种情况下的错误信息是什么?

更新:顺便说一句,您的证书已经采用 .pem(ASCII-base64 编码)格式。

【讨论】:

  • verify_peerfalse 是一个非常糟糕的建议...破坏来自 TLS 的所有安全功能。
  • 不是所有的安全功能,只是对等验证。它有助于查明 CA 证书的问题。
  • 不,因为如果它有效,人们会保持这种状态并且永远不会放回去,因此你失去了所有的安全性(真实性比机密性更重要,与直觉相反)。看看cs.utexas.edu/~shmat/shmat_ccs12.pdf
  • 此设置的 Apache 配置中有一个 SSLVerifyClient none 指令,因此它可能是错误或过期的 CA 证书。
  • 不相关。他的应用程序正在 8090 上打开一个 TLS 套接字。
【解决方案3】:

看起来 OpenSSL 不喜欢你的密钥。

您可以尝试致电http://php.net/manual/en/function.openssl-error-string.php 看看是否有更多信息说明原因?

或者根据这些可能相关的问题尝试不同的密钥文件组合,这些问题具有相同的错误:

GL

【讨论】:

  • 好的,谢谢,我刚刚添加了 openssl 错误消息以及证书文件在 apache 中的使用方式。
猜你喜欢
  • 2013-02-22
  • 2019-03-21
  • 2018-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-04
相关资源
最近更新 更多