带有 OpenSSL 签名证书的 PHP Websocket SSL Stunnel “坏证书”

Posted

技术标签:

【中文标题】带有 OpenSSL 签名证书的 PHP Websocket SSL Stunnel “坏证书”【英文标题】:PHP Websocket SSL Stunnel "bad certificate" with OpenSSL signed certificate 【发布时间】:2015-04-24 15:59:43 【问题描述】:

我知道还有很多其他类似的问题,但经过几天的尝试,我没有更多解决问题的想法。

我第一次体验 Websocket 连接,我需要构建一个简单的聊天,为此我正在尝试 phpWebSocketServer (https://github.com/ghedipunk/PHP-WebSockets),所以我测试了这个聊天示例 (https://github.com/Flynsarmy/PHPWebSocket-Chat) 和在我使用 ws 连接之前一切正常(这是一个众所周知的故事)。

对于 wss,我使用 pem 签名的 OpenSSL 证书设置了 Stunnel,服务已启动,端口 (9040 - 9000) 已打开,并且 websocket 服务器正确侦听端口 9000 (php ./server.php ),但我不知道为什么在 stunnel.log 中每个浏览器客户端调用中总是出现“错误证书”错误。

下面是所有可能有用的文件和日志。 从 Websocket php 服务器文件开始:

server.php

<?php
// prevent the server from timing out
set_time_limit(0);

// include the web sockets server script (the server is started at the far bottom of this file)
require 'class.PHPWebSocket.php';

// when a client sends data to the server
function wsOnMessage($clientID, $message, $messageLength, $binary) 
global $Server;
$ip = long2ip( $Server->wsClients[$clientID][6] );

// check if message length is 0
if ($messageLength == 0) 
    $Server->wsClose($clientID);
    return;


//The speaker is the only person in the room. Don't let them feel lonely.
if ( sizeof($Server->wsClients) == 1 )
    $Server->wsSend($clientID, "There isn't anyone else in the room, but I'll still listen to you. --Your Trusty Server");
else
    //Send the message to everyone but the person who said it
    foreach ( $Server->wsClients as $id => $client )
        if ( $id != $clientID )
            $Server->wsSend($id, "Visitor $clientID ($ip) said \"$message\"");


// when a client connects
function wsOnOpen($clientID)

global $Server;
$ip = long2ip( $Server->wsClients[$clientID][6] );

$Server->log( "$ip ($clientID) has connected." );

//Send a join notice to everyone but the person who joined
foreach ( $Server->wsClients as $id => $client )
    if ( $id != $clientID )
        $Server->wsSend($id, "Visitor $clientID ($ip) has joined the room.");


// when a client closes or lost connection
function wsOnClose($clientID, $status) 
global $Server;
$ip = long2ip( $Server->wsClients[$clientID][6] );

$Server->log( "$ip ($clientID) has disconnected." );

//Send a user left notice to everyone in the room
foreach ( $Server->wsClients as $id => $client )
    $Server->wsSend($id, "Visitor $clientID ($ip) has left the room.");


// start the server
$Server = new PHPWebSocket();
$Server->bind('message', 'wsOnMessage');
$Server->bind('open', 'wsOnOpen');
$Server->bind('close', 'wsOnClose');
// for other computers to connect, you will probably need to change this to your LAN IP or external IP,
// alternatively use: gethostbyaddr(gethostbyname($_SERVER['SERVER_NAME']))
$Server->wsStartServer('0.0.0.0', 9000);
?>

客户端聊天.php

<html>
<head>
<meta charset='UTF-8' />
<style>
    input, textarea border:1px solid #CCC;margin:0px;padding:0px

    #body max-width:800px;margin:auto
    #log width:100%;height:400px
    #message width:100%;line-height:20px
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="fancywebsocket.js"></script>
<script>
    var Server;

    function log( text ) 
        $log = $('#log');
        //Add text to log
        $log.append(($log.val()?"\n":'')+text);
        //Autoscroll
        $log[0].scrollTop = $log[0].scrollHeight - $log[0].clientHeight;
    

    function send( text ) 
        Server.send( 'message', text );
    

    $(document).ready(function() 
        log('Connecting...');
        Server = new FancyWebSocket('wss://xx.xx.xx.xx:9040');

        $('#message').keypress(function(e) 
            if ( e.keyCode == 13 && this.value ) 
                log( 'You: ' + this.value );
                send( this.value );

                $(this).val('');
            
        );

        //Let the user know we're connected
        Server.bind('open', function() 
            log( "Connected." );
        );

        //OH NOES! Disconnection occurred.
        Server.bind('close', function( data ) 
            log( "Disconnected." );
        );

        //Log any messages sent from server
        Server.bind('message', function( payload ) 
            log( payload );
        );

        Server.connect();
    );
</script>
</head>

<body>
<div id='body'>
    <textarea id='log' name='log' readonly='readonly'></textarea><br/>
    <input type='text' id='message' name='message' />
</div>
</body>

</html>

stunnel.conf

cert = /home/myuser/ssl-cert/ssl/stunnel.pem
key = /home/myuser/ssl-cert/mykey.key

chroot = /var/run/stunnel
pid = /stunnel.pid
client = no
fips = no

sslVersion = all
options = NO_SSLv2 ;also commented but same result
options = NO_SSLv3 ;also commented but same result

accept = foobar

socket = l:TCP_NODELAY=1
socket = r:TCP_NODELAY=1

debug = 7
output = /var/log/stunnel/stunnel.log

[stunnel]
accept = 0.0.0.0:9040
connect = 127.0.0.1:9000

我的 OpenSSL 签名证书来自 ssls (COMODO) CA,正如 stunnel howto 中所建议的,我使用服务器机器的主机名作为通用名称,我们称之为 (mydomain.com),但我还有另一个OpenSSL证书,我的网站域名作为https模式的通用名称(www.mydomain.com),我都使用过,但错误仍然相同。

无论如何,COMODO 给我发送了 4 个不同的文件,mydomain_com.crt、COMODORSADomainValidationSecureServerCA.crt、COMODORSAAddTrustCA.crt、AddTrustExternalCARoot.crt,我已经按照这种方式将它们分类到 stunnel.pem 文件中。

stunnel.log(服务启动时)

2015.04.24 17:39:30 LOG7[13406:139929686013888]: Snagged 64 random bytes from /dev/urandom
2015.04.24 17:39:30 LOG7[13406:139929686013888]: RAND_status claims sufficient entropy for the PRNG
2015.04.24 17:39:30 LOG7[13406:139929686013888]: PRNG seeded successfully
2015.04.24 17:39:30 LOG7[13406:139929686013888]: Configuration SSL options: 0x03000000
2015.04.24 17:39:30 LOG7[13406:139929686013888]: SSL options set: 0x03000004
2015.04.24 17:39:30 LOG7[13406:139929686013888]: Certificate: /home/myuser/ssl-cert/ssl/stunnel.pem
2015.04.24 17:39:30 LOG7[13406:139929686013888]: Certificate loaded
2015.04.24 17:39:30 LOG7[13406:139929686013888]: Key file: /home/myuser/ssl-cert/mykey.key
2015.04.24 17:39:30 LOG7[13406:139929686013888]: Private key loaded
2015.04.24 17:39:30 LOG7[13406:139929686013888]: SSL context initialized for service stunnel
2015.04.24 17:39:30 LOG7[13406:139929686013888]: FIPS mode disabled
2015.04.24 17:39:31 LOG5[13406:139929686013888]: stunnel 4.29 on x86_64-redhat-linux-gnu with OpenSSL 1.0.1e-fips 11 Feb 2013
2015.04.24 17:39:31 LOG5[13406:139929686013888]: Threading:PTHREAD SSL:ENGINE,FIPS Sockets:POLL,IPv6 Auth:LIBWRAP
2015.04.24 17:39:31 LOG6[13406:139929686013888]: file ulimit = 1024 (can be changed with 'ulimit -n')
2015.04.24 17:39:31 LOG6[13406:139929686013888]: poll() used - no FD_SETSIZE limit for file descriptors
2015.04.24 17:39:31 LOG5[13406:139929686013888]: 500 clients allowed
2015.04.24 17:39:31 LOG7[13406:139929686013888]: FD 10 in non-blocking mode
2015.04.24 17:39:31 LOG7[13406:139929686013888]: FD 11 in non-blocking mode
2015.04.24 17:39:31 LOG7[13406:139929686013888]: FD 12 in non-blocking mode
2015.04.24 17:39:31 LOG7[13406:139929686013888]: SO_REUSEADDR option set on accept socket
2015.04.24 17:39:31 LOG7[13406:139929686013888]: stunnel bound to 0.0.0.0:9040
2015.04.24 17:39:31 LOG7[13412:139929686013888]: Created pid file /stunnel.pid

stunnel.log(客户端 wss 调用)

2015.04.24 17:45:10 LOG7[13412:139929686013888]: stunnel accepted FD=13 from xx.xx.xx.xx:62481
2015.04.24 17:45:10 LOG7[13412:139929686116096]: stunnel started
2015.04.24 17:45:10 LOG7[13412:139929686116096]: FD 13 in non-blocking mode
2015.04.24 17:45:10 LOG7[13412:139929686116096]: TCP_NODELAY option set on local socket
2015.04.24 17:45:10 LOG7[13412:139929686116096]: Waiting for a libwrap process
2015.04.24 17:45:10 LOG7[13412:139929686116096]: Acquired libwrap process #0
2015.04.24 17:45:10 LOG7[13412:139929686116096]: Releasing libwrap process #0
2015.04.24 17:45:10 LOG7[13412:139929686116096]: Released libwrap process #0
2015.04.24 17:45:10 LOG7[13412:139929686116096]: stunnel permitted by libwrap from xx.xx.xx.xx:62481
2015.04.24 17:45:10 LOG5[13412:139929686116096]: stunnel accepted connection from xx.xx.xx.xx:62481
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL state (accept): before/accept initialization
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL state (accept): SSLv3 read client hello A
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL state (accept): SSLv3 write server hello A
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL state (accept): SSLv3 write certificate A
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL state (accept): SSLv3 write server done A
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL state (accept): SSLv3 flush data
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL state (accept): SSLv3 read client key exchange A
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL state (accept): SSLv3 read finished A
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL state (accept): SSLv3 write session ticket A
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL state (accept): SSLv3 write change cipher spec A
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL state (accept): SSLv3 write finished A
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL state (accept): SSLv3 flush data
2015.04.24 17:45:10 LOG7[13412:139929686116096]:    0 items in the session cache
2015.04.24 17:45:10 LOG7[13412:139929686116096]:    0 client connects (SSL_connect())
2015.04.24 17:45:10 LOG7[13412:139929686116096]:    0 client connects that finished
2015.04.24 17:45:10 LOG7[13412:139929686116096]:    0 client renegotiations requested
2015.04.24 17:45:10 LOG7[13412:139929686116096]:    1 server connects (SSL_accept())
2015.04.24 17:45:10 LOG7[13412:139929686116096]:    1 server connects that finished
2015.04.24 17:45:10 LOG7[13412:139929686116096]:    0 server renegotiations requested
2015.04.24 17:45:10 LOG7[13412:139929686116096]:    0 session cache hits
2015.04.24 17:45:10 LOG7[13412:139929686116096]:    0 external session cache hits
2015.04.24 17:45:10 LOG7[13412:139929686116096]:    0 session cache misses
2015.04.24 17:45:10 LOG7[13412:139929686116096]:    0 session cache timeouts
2015.04.24 17:45:10 LOG6[13412:139929686116096]: SSL accepted: new session negotiated
2015.04.24 17:45:10 LOG6[13412:139929686116096]: Negotiated ciphers: AES128-SHA SSLv3 Kx=RSA Au=RSA Enc=AES(128) Mac=SHA1
2015.04.24 17:45:10 LOG7[13412:139929686116096]: FD 14 in non-blocking mode
2015.04.24 17:45:10 LOG6[13412:139929686116096]: connect_blocking: connecting 127.0.0.1:9000
2015.04.24 17:45:10 LOG7[13412:139929686116096]: connect_blocking: s_poll_wait 127.0.0.1:9000: waiting 10 seconds
2015.04.24 17:45:10 LOG5[13412:139929686116096]: connect_blocking: connected 127.0.0.1:9000
2015.04.24 17:45:10 LOG5[13412:139929686116096]: stunnel connected remote server from 127.0.0.1:50258
2015.04.24 17:45:10 LOG7[13412:139929686116096]: Remote FD=14 initialized
2015.04.24 17:45:10 LOG7[13412:139929686116096]: TCP_NODELAY option set on remote socket
2015.04.24 17:45:10 LOG7[13412:139929686116096]: SSL alert (read): fatal: bad certificate
2015.04.24 17:45:10 LOG3[13412:139929686116096]: SSL_read: 14094412: error:14094412:SSL routines:SSL3_READ_BYTES:sslv3 alert bad certificate
2015.04.24 17:45:10 LOG5[13412:139929686116096]: Connection reset: 0 bytes sent to SSL, 0 bytes sent to socket
2015.04.24 17:45:10 LOG7[13412:139929686116096]: stunnel finished (0 left)

我也试过 openssl 客户端连接

openssl s_client -connect xx.xx.xx.xx:9040

它有效!

请帮忙!

【问题讨论】:

【参考方案1】:

我也尝试过 openssl 客户端连接...它可以工作!

不清楚什么在起作用:可能连接和 SSL 握手正在起作用。如果信任链对您有效,请获取Verify return code: 0 (ok)。但是openssl s_client 不做任何主机名检查,而浏览器做。

Server = new FancyWebSocket('wss://xx.xx.xx.xx:9040');

这仅在您的证书实际上是针对给定 IP 时才有效。但是我怀疑您是否为特定 IP 购买了证书,因为通常会为主机名购买证书。由于您没有通过证书中包含的名称访问服务器,因此主机名验证将失败。

【讨论】:

谢谢!解决了!将 wss://xx.xx.xx.xx:9040 更改为 wss://mydomain.com:9040 现在已正确连接 @Steffen Ullrich,非常感谢!!我花了一个多星期解决这个奇怪的问题,我的代理返回不同类型的错误,说我的证书很糟糕,而一切都设置正确。几天后我把头发拔了:-)

以上是关于带有 OpenSSL 签名证书的 PHP Websocket SSL Stunnel “坏证书”的主要内容,如果未能解决你的问题,请参考以下文章

通过 Windows 中的 OpenSSL 为我的 Android 应用程序创建带有自签名证书的 PKCS#12 文件

怎么在windows上openssl颁发代码签名证书?

PHP的OpenSSL加密扩展学习:证书操作

PHP的OpenSSL加密扩展学习:证书操作

openssl 自签名证书 - 安装openssl(一)

OpenSSL生成HTTPS自签名证书