php - 如何使用 PHP 读取 TLS 证书 websockets?

标签 php ssl websocket serversocket phpwebsocket

我正在尝试连接到由 PHP 创建的安全网络套接字,但由于某种原因它无法正常工作。证书文件对于 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 网络服务器中使用的两个文件。如果需要,可以将该文件转换为 pem 文件。但我认为它也应该在 php 中与这两个文件一起工作,不是吗?

为了更好的测试,我们使用带有 let's encrypt 证书的隔 ionic 域。因为我们可以完全访问该服务器。但是,从 certifacte 生成器中我只得到了这两个提到的文件。对于 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 文件,但我的证书看起来与网站上提到的不同,而且我不知道要将什么确切地包含到 PEM 文件中,php 需要: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');

Warning: stream_socket_accept(): Unable to set private key file `/opt/psa/var/certificates/cert-0x8zHR' in ... on line ...

Warning: stream_socket_accept(): Failed to enable crypto in ... on line ...

Warning: stream_socket_accept(): accept failed: Success in ... on line ...

Warning: socket_read() expects parameter 1 to be resource, boolean given in ... on line ...

Notice: Undefined offset: 1 in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_close() expects parameter 1 to be resource, boolean given in ... on line ...

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');

Warning: stream_socket_accept(): Unable to set private key file `/opt/psa/var/certificates/cert-Du9H8N' in ... on line ...

Warning: stream_socket_accept(): Failed to enable crypto in ... on line ...

Warning: stream_socket_accept(): accept failed: Success in ... on line ...

Warning: socket_read() expects parameter 1 to be resource, boolean given in ... on line ...

Notice: Undefined offset: 1 in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_close() expects parameter 1 to be resource, boolean given in ... on line ...

还有(只是 cert-0x8zHR 与 cert-Du9H8N 的串联)

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

Warning: stream_socket_accept(): Could not get peer certificate in ... ...

Warning: stream_socket_accept(): Failed to enable crypto in ... ...

Warning: stream_socket_accept(): accept failed: Success in ... ...

Warning: socket_read() expects parameter 1 to be resource, boolean given in ... on line ...

Notice: Undefined offset: 1 in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... ...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... ...

Warning: socket_close() expects parameter 1 to be resource, boolean given in ... on line ...

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

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

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

此外,如果我执行此命令:

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

我有两个不同的模数字符串。但我不知道为什么,也不知道如何解决这个问题。这两个文件都用于 apache web 服务器虚拟主机配置,并且工作正常:

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

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

最佳答案

你说:

My generated certificate files look like this:

File /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-----

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

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

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

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

在上面的文件中,两个证书可能是 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,因为这显然太低了,暴露了太多您迷路的选项。

关于php - 如何使用 PHP 读取 TLS 证书 websockets?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50288448/

相关文章:

ruby-on-rails - Rails 5 Actioncable 没有 channel 的全局消息

spring - 哎呀!失去与未定义的连接 - 连接建立后立即失去连接

php - 为什么我的代码没有从我的数据库中发布数据?

PHPWord - 我可以在一个表格单元格中连接多种字体样式吗?

java - 是否可以将自定义信任库与 SOAPConnection 一起使用?

php - 如何修复网络解决方案 ssl 代理的错误

javascript - 在 websocket 中为 onmessage 中的变量赋值

PHP 命名空间和要求

php - 学校网站的 MySQL 表

Ubuntu 18.04 上的 Django 站点在安装 SSL 后无法使用 Apache2