能否在 TcpConnection 的 stream_socket_enable_crypto 之前, 提供一个 beforeSslHandshake 回调方法来修改 socket 的 contentx, 来实现这个功能?
看了些资料, SSL在握手阶段, 客户端发的第一个Hello握手包里有域名, 要实现这个功能, 必须取得这个包里的Extension server_name数据. 现有的socket和stream函数好像没有这样的功能. 不知道是不是要用openssl自己实现ssl握手过程才行.
区分域名的话看起来要openssl手动ssl握手了,这个难度有点大。
我看了TLS协议握手过程, 硬编码肯定可以实现, 但是效率存疑. 况且这么大的东西要完全按照规范写到稳定健壮, 对我这个小项目就是杀鸡用牛刀了, 不太现实.
下个项目会需要解析TLS握手包, 到时候再看.
$ctx = stream_context_create(["ssl" => [ "local_cert" => "/path/to/cert.pem", "SNI_server_certs" => [ "domain1.com" => "/path/to/domain1.pem", "*.domain2.com" => "/path/to/domain2.pem", "domain3.com" => "/path/to/domain3.pem" ] ]]);
找到一些资料, PHP 5.6以后, stream_socket_server 的 context 可以支持 SNI (Server Name Indication). 这样的话, 这个问题可以解决一半了, 服务端可以支持针对不同的域名使用不同的证书.
剩下的问题就是, 证书数量变化时, 在不重启 Server 的前提下, 如何平滑地重载 SNI 证书列表.
关于 SNI_server_certs 在 https://github.com/php/php-src/blob/PHP-5.6/NEWS 2349行有描述, 用法在 https://stackoverflow.com/questions/20865301/php-server-side-sni-support 有人回复, 我还没测试. 但是在php官方在线手册里都找不到这个选项的描述
https://github.com/php/php-src/blob/PHP-7.2.25/ext/openssl/xp_ssl.c 查看发现5.6和7.2的源码中都有SNI_server_certs的支持,, 应该没问题, 只是官方文档没更新而已.
这个方式可以用在worker的tcp监听了吗,代码能贴一下吗,我也在研究这个. 我打算,只要能实现监听多个https就行,正是变化的时候就在业务事件里热重启
@4787: 看下面的回复
此问题已解决, 目前我这里运行良好. 关键代码和说明如下:
第一步: 声明 context, 启动服务. $context 的 SNI_server_certs 部分留空, 但最终要将 SNI_server_certs 部分填充为注释所示的样子.
$context = [ 'ssl' => [ 'verify_peer' => false, 'disable_compression' => true, 'SNI_enabled' => true, 'SNI_server_certs' => [ /* "*.domain1.com" => [ 'local_cert' => "{$this->certFileRoot}/domain1.com/_.domain1.com.pem", 'local_pk' => "{$this->certFileRoot}/domain1.com/_.domain1.com.key", ], "*.domain2.com" => [ 'local_cert' => "{$this->certFileRoot}/domain2.com/_.domain2.com.crt", 'local_pk' => "{$this->certFileRoot}/domain2.com/_.domain2.com.key", ], "domain3.com" => [ 'local_cert' => "{$this->certFileRoot}/domain3.com/domain3.com.crt", 'local_pk' => "{$this->certFileRoot}/domain3.com/domain3.com.key", ], "www.domain3.com" => [ 'local_cert' => "{$this->certFileRoot}/domain3.com/www.domain3.com.crt", 'local_pk' => "{$this->certFileRoot}/domain3.com/www.domain3.com.key", ], */ ], ], ]; $server = new WorkerX("http://0.0.0.0:443", $context); $server->count = 10; $server->transport = 'ssl'; $server->name = 'Https Server';
第二步: 继承并重写 Worker 类, 以便于可以在运行时设置 stream_context 原Worker类中, 使用一个 protected 的 _context 属性保存socket上下文, 外部无法直接修改, 所以需要继承 Worker后,在我们实现的子类中修改.
另外, socket 上下文在php中是一个 resource 类型, 反映到php中可以视为内存地址引用. 对此变量的赋值操作不会创建新的对象.
class WorkerX extends \Workerman\Worker { public function contextGetOptions() : array { if(is_resource($this->_context)) { return stream_context_get_options($this->_context); } return []; } public function contextSetOptions(array $options) : bool { if(is_resource($this->_context)) { return stream_context_set_option($this->_context, $options); } return false; } }
第三步: 在$server的onWorkerStart回调中, 通过Channel注册事件, 允许外部通知服务器动态载入证书信息. 可以启动另外一个专用的api服务, api服务接收管理端的调用后, 发布 EVENT_REFRESH_CERT 事件, 事件数据中标明需要重载哪个站点的证书.
$server->onWorkerStart = function($worker) { WorkerDI::init($worker); $this->refreshCert($worker, 0); \Channel\Client::on('EVENT_REFRESH_CERT', function($eventData) use ($worker) { $siteId = intval($eventData['site_id']); $this->refreshCert($worker, $siteId); }); }; private function refreshCert(WorkerX $worker, int $siteId = 0) { $sslContextOptions = $worker->contextGetOptions(); // 此处通过 $siteId 查询数据库, 取得站点绑定的域名, 和域名对应的证书文件路径 $domain = '*.domain1.com'; $certFilePath = '/path/to/certFile.crt'; $keyFilePath = '/path/to/certFile.key'; $sslContextOptions['ssl']['SNI_server_certs'][$domain] = [ 'local_cert' => $certFilePath, 'local_pk' => $pkFilePath, ]; $setResult = $worker->contextSetOptions($sslContextOptions); echo "设置" . ($setResult ? "成功" : "失败") . "\n"; }
刷新页面, 此时证书已经在服务中生效, 功能完成.
学习了
很强,学习了
看了些资料, SSL在握手阶段, 客户端发的第一个Hello握手包里有域名, 要实现这个功能, 必须取得这个包里的Extension server_name数据. 现有的socket和stream函数好像没有这样的功能. 不知道是不是要用openssl自己实现ssl握手过程才行.
区分域名的话看起来要openssl手动ssl握手了,这个难度有点大。
我看了TLS协议握手过程, 硬编码肯定可以实现, 但是效率存疑.
况且这么大的东西要完全按照规范写到稳定健壮, 对我这个小项目就是杀鸡用牛刀了, 不太现实.
下个项目会需要解析TLS握手包, 到时候再看.
找到一些资料, PHP 5.6以后, stream_socket_server 的 context 可以支持 SNI (Server Name Indication).
这样的话, 这个问题可以解决一半了, 服务端可以支持针对不同的域名使用不同的证书.
剩下的问题就是, 证书数量变化时, 在不重启 Server 的前提下, 如何平滑地重载 SNI 证书列表.
关于 SNI_server_certs 在 https://github.com/php/php-src/blob/PHP-5.6/NEWS 2349行有描述, 用法在 https://stackoverflow.com/questions/20865301/php-server-side-sni-support 有人回复, 我还没测试. 但是在php官方在线手册里都找不到这个选项的描述
https://github.com/php/php-src/blob/PHP-7.2.25/ext/openssl/xp_ssl.c 查看发现5.6和7.2的源码中都有SNI_server_certs的支持,, 应该没问题, 只是官方文档没更新而已.
这个方式可以用在worker的tcp监听了吗,代码能贴一下吗,我也在研究这个.
我打算,只要能实现监听多个https就行,正是变化的时候就在业务事件里热重启
@4787: 看下面的回复
此问题已解决, 目前我这里运行良好. 关键代码和说明如下:
第一步: 声明 context, 启动服务.
$context 的 SNI_server_certs 部分留空, 但最终要将 SNI_server_certs 部分填充为注释所示的样子.
第二步: 继承并重写 Worker 类, 以便于可以在运行时设置 stream_context
原Worker类中, 使用一个 protected 的 _context 属性保存socket上下文, 外部无法直接修改, 所以需要继承 Worker后,在我们实现的子类中修改.
另外, socket 上下文在php中是一个 resource 类型, 反映到php中可以视为内存地址引用. 对此变量的赋值操作不会创建新的对象.
第三步: 在$server的onWorkerStart回调中, 通过Channel注册事件, 允许外部通知服务器动态载入证书信息.
可以启动另外一个专用的api服务, api服务接收管理端的调用后, 发布 EVENT_REFRESH_CERT 事件, 事件数据中标明需要重载哪个站点的证书.
刷新页面, 此时证书已经在服务中生效, 功能完成.
学习了
很强,学习了