简体   繁体   English

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

[英]How to read TLS certificates websockets using PHP?

I am trying to connect to a secure websocket created by PHP, but for some reason it doesn't work. 我试图连接到由PHP创建的安全websocket,但由于某种原因它不起作用。 The certificate files are readable for PHP. 证书文件对于PHP是可读的。

This is my code so far (PHP side; stripped down code for simplicity): 到目前为止,这是我的代码(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);

The client side: 客户方:

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

I do not have any *.pem file. 我没有任何* .pem文件。 Just the two files, that are used in apache web server. 只是apache web服务器中使用的两个文件。 It would be possible to convert that files into a pem file, if needed. 如果需要,可以将该文件转换为pem文件。 But I think it should also work in php with these both files, shouldn't it? 但我认为它应该在php中使用这两个文件,不应该吗?

For a better testing, we are using an isolated subdomain with an let's encrypt certificate. 为了更好的测试,我们使用带有let的加密证书的隔离子域。 Because we got full access to this server. 因为我们可以完全访问此服务器。 However, from the certifacte generator I got only those two mentioned files. 但是,从certifacte生成器我只得到那两个提到的文件。 For the web server it works perfectly. 对于Web服务器,它可以很好地工作。 But how to do the same for a websocket in php? 但是如何为php中的websocket做同样的事情呢?

Now, with this code, after sending some messages to the client the server script tells me, that it was not able to get the peer from the certificates. 现在,使用此代码,在向客户端发送一些消息后,服务器脚本告诉我,它无法从证书中获取对等方。 I don't know what this message means and how to fix that. 我不知道这条消息的含义以及如何解决这个问题。 I also alreay tried to swap local_cert and local_pk with each other, but it hasn't helped anyway. 我也试图互相交换local_certlocal_pk ,但无论如何它都没有帮助。

Edit: After researching a while, it turns out, that php fails with every combination with another error. 编辑:经过一段时间的研究,事实证明,PHP失败了每一个组合与另一个错误。

My generated certificate files look like this: 我生成的证书文件如下所示:

File /opt/psa/var/certificates/cert-0x8zHR: 文件/ 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-----

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

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

All lines from the certificate strings from both of them end at character position 65. 来自两个证书字符串的所有行都在字符位置65处结束。

As you can read in php documentation, php expects to get a file in PEM format: http://php.net/manual/en/context.ssl.php#context.ssl.local-cert 正如你可以在php文档中看到的那样,php希望得到一个PEM格式的文​​件: http//php.net/manual/en/context.ssl.php#context.ssl.local-cert

I found this tutorial to convert my two files into one PEM file, but my certificates look different to mentioned on the site and also I don't know what to exactly include into the PEM file, that php needs: https://www.digicert.com/ssl-support/pem-ssl-creation.htm 我发现本教程将我的两个文件转换为一个PEM文件,但我的证书看起来与网站上提到的不同,而且我不知道究竟要包含在PEM文件中,PHP需要: https:// www。 digicert.com/ssl-support/pem-ssl-creation.htm

Edit 2: As mentioned below, here are the exact erros I get: 编辑2:如下所述,这里是我得到的确切错误:

with

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 ... 警告:stream_socket_accept():无法在...中设置私钥文件`/ opt / psa / var / certificates / cert-0x8zHR'...

Warning: stream_socket_accept(): Failed to enable crypto in ... on line ... 警告:stream_socket_accept():无法在...中启用加密...

Warning: stream_socket_accept(): accept failed: Success in ... on line ... 警告:stream_socket_accept():接受失败:成功...在线......

Warning: socket_read() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_read()期望参数1是资源,boolean在...中给出...

Notice: Undefined offset: 1 in ... on line ... 注意:未定义的偏移量:1 ...在线...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_close() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_close()期望参数1是资源,在线...给出的布尔值...

And with

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 ... 警告:stream_socket_accept():无法在...中设置私钥文件`/ opt / psa / var / certificates / cert-Du9H8N'...

Warning: stream_socket_accept(): Failed to enable crypto in ... on line ... 警告:stream_socket_accept():无法在...中启用加密...

Warning: stream_socket_accept(): accept failed: Success in ... on line ... 警告:stream_socket_accept():接受失败:成功...在线......

Warning: socket_read() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_read()期望参数1是资源,boolean在...中给出...

Notice: Undefined offset: 1 in ... on line ... 注意:未定义的偏移量:1 ...在线...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_close() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_close()期望参数1是资源,在线...给出的布尔值...

And with (just a concatenation of cert-0x8zHR with cert-Du9H8N) 并且(只是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 ... ... 警告:stream_socket_accept():无法获得对等证书......

Warning: stream_socket_accept(): Failed to enable crypto in ... ... 警告:stream_socket_accept():无法启用加密......

Warning: stream_socket_accept(): accept failed: Success in ... ... 警告:stream_socket_accept():接受失败:成功......

Warning: socket_read() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_read()期望参数1是资源,boolean在...中给出...

Notice: Undefined offset: 1 in ... on line ... 注意:未定义的偏移量:1 ...在线...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_write()期望参数1是资源,在线...给出的布尔值...

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... ... 警告:socket_write()期望参数1是资源,boolean在...中给出

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... ... 警告:socket_write()期望参数1是资源,boolean在...中给出

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... ... 警告:socket_write()期望参数1是资源,boolean在...中给出

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... ... 警告:socket_write()期望参数1是资源,boolean在...中给出

Warning: socket_write() expects parameter 1 to be resource, boolean given in ... ... 警告:socket_write()期望参数1是资源,boolean在...中给出

Warning: socket_close() expects parameter 1 to be resource, boolean given in ... on line ... 警告:socket_close()期望参数1是资源,在线...给出的布尔值...

Edit 3: Yes, indeed, there is an openssl error. 编辑3:是的,的确有一个openssl错误。 After the third socket_stream_accept warning I now get this error by just using the code from the php doc example: 在第三次socket_stream_accept警告后我现在通过使用php doc示例中的代码得到此错误:

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

I searched for this error in the internet. 我在互联网上搜索了这个错误。 They say, if this error appears, I have chosen the wrong certificate file. 他们说,如果出现此错误,我选择了错误的证书文件。

Also, if I play this command: 另外,如果我播放此命令:

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

I got two different strings for the modulus. 模数有两个不同的字符串。 But I don't know why, neither how to fix this. 但我不知道为什么,也不知道如何解决这个问题。 These both files are used in apache web server vhost config and it works fine: 这两个文件都在apache web server vhost config中使用,它可以正常工作:

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

PS: If you know any tool for local conversion to a pem file, please let me know. PS:如果你知道任何本地转换为pem文件的工具,请告诉我。 Online conversion is impossible. 在线转换是不可能的。

You said: 你说:

My generated certificate files look like this: 我生成的证书文件如下所示:

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

-----BEGIN CERTIFICATE REQUEST----- some letters -----END CERTIFICATE REQUEST----- -----开始证书申请-----一些信件-----结束证书申请-----

-----BEGIN PRIVATE KEY----- some letters -----END PRIVATE KEY----- ----- BEGIN PRIVATE KEY -----一些字母----- END PRIVATE KEY -----

-----BEGIN CERTIFICATE----- some letters -----END CERTIFICATE----- -----开始证书-----一些信件----- END CERTIFICATE -----

-----BEGIN CERTIFICATE----- some letters -----END CERTIFICATE----- -----开始证书-----一些信件----- END CERTIFICATE -----

This is wrong on so many levels. 这在很多层面都是错误的。

First the "CERTIFICATE REQUEST" part is completely useless as soon as you get the certificate. 首先,“证书请求”部分一旦获得证书就完全没用了。 You can just ignore this part. 你可以忽略这部分。

Now copy the "PRIVATE KEY" part, with headers in one file. 现在复制“PRIVATE KEY”部分,标题在一个文件中。 This is your private key, you can use everywhere you have options "local_pk" or software asking for a key. 这是您的私钥,您可以在任何地方使用“local_pk”选项或要求输入密钥的软件。

Then you have two certificates. 然后你有两个证书。 And another one in another file. 另一个在另一个文件中。 You will need to sort all of this. 您需要对所有这些进行排序。 If you had given the real certificates content (which are public content) people could have help you better. 如果您已经提供了真实的证书内容(这是公共内容),那么人们可以更好地帮助您。 Here with just "some letters" we can only guess. 这里只有“一些字母”,我们只能猜测。

In the above file the two certificates may be the CA and intermediate. 在上面的文件中,两个证书可以是CA和中间证书。 You should copy them both to another file, and this will correspond to the "ca_cert" option. 您应该将它们复制到另一个文件,这将对应于“ca_cert”选项。

I guess that the second file is your try certificate (only a guess again, without your details). 我猜第二个文件是你的尝试证书(只有再次猜测,没有你的详细信息)。 Use it everywhere you have "local_cert" or asking for a certificate. 在任何有“local_cert”或要求证书的地方使用它。 This certificate should match the private key file (you can not verify that by hand, you need tooling). 此证书应与私钥文件匹配(您无法手动验证,需要工具)。

Once you created all the correct files, the first one is not to be used anymore. 一旦你创建了所有正确的文件,第一个文件就不再使用了。 I would recommend using better semantics for the file name because cert-X and cert-Y will be quite difficult. 我建议为文件名使用更好的语义,因为cert-Xcert-Y将非常困难。 Use instead the website name so that you have files like www.example.com.cert , www.example.com-ca.cert and www.example.com.key , so that it is immediately clear what is what. 请使用网站名称,以便您拥有www.example.com.certwww.example.com-ca.certwww.example.com.key ,以便立即清楚它是什么。 Or with a directory called www.example.com/ , and then cert.pem , ca.pem and key.pem inside the directory. 或者使用名为www.example.com/的目录,然后在目录中使用cert.pemca.pemkey.pem The extensions by themselves are not used by the software and irrespective to the content, it is only up to you to define things that make sense. 软件不使用扩展本身,无论内容如何,​​只有您自己才能定义有意义的内容。

So first sort all of this, so that your question is clearer. 所以首先对所有这些进行排序,以便您的问题更加清晰。 Right now it seems you are trying stuff blindly until coming to something that works, which is not the ideal situation in the realm of security and TLS stuff. 现在看起来你正在盲目地尝试一些东西,直到找到有效的东西,这不是安全和TLS领域的理想情况。

If possible I would also encourage you to try using higher level abstraction library for TLS handling, as this is clearly far too low, exposing so many options that you are getting lost. 如果可能的话,我也会鼓励你尝试使用更高级别的抽象库来进行TLS处理,因为这显然太低了,暴露了很多你迷失的选项。

It looks like OpenSSL doesn't like your key. 看起来OpenSSL不喜欢你的密钥。

You could try calling http://php.net/manual/en/function.openssl-error-string.php to see if there is more info available about why? 您可以尝试调用http://php.net/manual/en/function.openssl-error-string.php来查看是否有更多关于原因的信息?

Or try different combinations of key files, as per these possibly related questions which feature the same error: 或者尝试不同的密钥文件组合,根据具有相同错误的这些可能相关的问题:

GL GL

Try this: 尝试这个:

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

If that doesn't work, try to comment out second line ( ca_flie ) and set verify_peer to false 如果这不起作用,请尝试注释掉第二行( ca_flie )并将verify_peer设置为false

What are error messages in both cases? 两种情况下的错误消息是什么?

Update: BTW, your certificates are already in .pem (ASCII-base64-encoded) format. 更新:BTW,您的证书已经是.pem (ASCII-base64编码)格式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM