大家好,我是yangyang.本计划不会最近不会更新mq相关文章,奈何今天使用tls连接生成证书的时候遇到了一个坑,于是,给大家分享一下.
SSL/TLS 证书准备
通常来说,我们会需要数字证书来保证 TLS 通讯的强认证。数字证书的使用本身是一个三方协议,除了通讯双方,还有一个颁发证书的受信第三方,有时候这个受信第三方就是一个 CA。和 CA 的通讯,一般是以预先发行证书的方式进行的。也就是在开始 TLS 通讯的时候,我们需要至少有 2 个证书,一个 CA 的,一个 EMQX 的,EMQX 的证书由 CA 颁发,并用 CA 的证书验证。
获得一个真正受外界信任的证书需要到证书服务提供商进行购买。在实验室环境,我们也可以用自己生成的证书来模拟这个过程。下面我们分别以这两种方式来说明 EMQX 服务器的 SSL/TLS 启用过程。
注意: 购买证书与自签名证书的配置,读者根据自身情况只需选择其中一种进行测试。
购买证书
如果有购买证书的话,就不需要自签名证书。
为方便 EMQX 配置,请将购买的证书文件重命名为 emqx.crt,证书密钥重命名为 emqx.key。
自签名证书
在这里,我们假设您的系统已经安装了 OpenSSL。使用 OpenSSL 附带的工具集就可以生成我们需要的证书了。
首先,我们需要一个自签名的 CA 证书。生成这个证书需要有一个私钥为它签名,可以执行以下命令来生成私钥:
openssl genrsa -out ca.key 2048
这个命令将生成一个密钥长度为 2048 的密钥并保存在 ca.key 中。有了这个密钥,就可以用它来生成 EMQX 的根证书了:
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.pem
查看 CA 证书信息(可选):
openssl x509 -in ca.pem -noout -text
根证书是整个信任链的起点,如果一个证书的每一级签发者向上一直到根证书都是可信的,那个我们就可以认为这个证书也是可信的。有了这个根证书,我们就可以用它来给其他实体签发实体证书了。
实体(在这里指的是 EMQX)也需要一个自己的私钥对来保证它对自己证书的控制权。生成这个密钥的过程和上面类似:
openssl genrsa -out emqx.key 2048
新建 openssl.cnf 文件,
- req_distinguished_name :根据情况进行修改,
- alt_names: BROKER_ADDRESS 修改为 EMQX 服务器实际的 IP 或 DNS 地址,例如:IP.1 = 127.0.0.1,或 DNS.1 = broker.xxx.com (如果在内网测试发现连不上,可以本地host `127.0.0.1 borker.test.com` 然后这里就写:IP.1=127.0.0.1 DNS.1=borker.test.com )
[req]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
countryName = CN
stateOrProvinceName = Zhejiang
localityName = Hangzhou
organizationName = EMQX
commonName = Server certificate
[req_ext]
subjectAltName = @alt_names
[v3_req]
subjectAltName = @alt_names
[alt_names]
IP.1 = BROKER_ADDRESS
DNS.1 = BROKER_ADDRESS
然后以这个密钥和配置签发一个证书请求:
openssl req -new -key ./emqx.key -config openssl.cnf -out emqx.csr
然后以根证书来签发 EMQX 的实体证书:
openssl x509 -req -in ./emqx.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out emqx.pem -days 3650 -sha256 -extensions v3_req -extfile openssl.cnf
查看 EMQX 实体证书(可选):
openssl x509 -in emqx.pem -noout -text
验证 EMQX 实体证书,确定证书是否正确:
$ openssl verify -CAfile ca.pem emqx.pem
emqx.pem: OK
准备好证书后,我们就可以启用 EMQX 的 TLS/SSL 功能了。
SSL/TLS 启用及验证
在 EMQX 中 mqtt:ssl 的默认监听端口为 8883。
MQTT 连接测试
注意:自签证书必须要传递ca根证书,同时如果emqx开启客户端证书验证时,如果上传了客户端证书和key就必须要传入对的或者不传.购买证书直接选第一个证书类型.
php workerman/mqtt 接入代码
public function onWorkerStart(Worker $worker)
{
$mqttConfig = config('mqtt.fbox');
if (empty($mqttConfig['listen'])) {
return;
}
$options = [];
!empty($mqttConfig['username']) && $options['username'] = $mqttConfig['username'];
!empty($mqttConfig['password']) && $options['password'] = $mqttConfig['password'];
if (0 === strpos($mqttConfig['listen'], 'mqtts') || 0 === strpos($mqttConfig['listen'], 'wss') ) {
if (empty($mqttConfig['ssl_ca'])) {
echo '[FBOX]', '请申请并配置证书', PHP_EOL;
return;
}
$verifyPeer = false;
$allowSelfSigned = false;
if ($mqttConfig['ssl_ca_type'] === 'selfSign') {
$verifyPeer = true;
$allowSelfSigned = true;
}
$options['ssl'] = [
'verify_peer' => $verifyPeer, // 如果是自签名证书需要设置为true,因为需要验证根证书,自行购买不用
'allow_self_signed' => $allowSelfSigned,
'cafile' => $mqttConfig['ssl_ca']
];
if (!empty($mqttConfig['ssl_key'])) {
$options['ssl']['local_pk'] = $mqttConfig['ssl_key'];
}
if (!empty($mqttConfig['ssl_cert'])) {
$options['ssl']['local_cert'] = $mqttConfig['ssl_cert'];
}
}
$this->connectMQTT($mqttConfig['listen'], $options);
}
protected function connectMQTT(string $addr, array $options = [])
{
// TODO: Implement connectMQTT() method.
$this->client = new Client($addr, $options);
$this->client->onConnect = [$this, 'onMqttConnect'];
// $this->client->onReconnect = [$this, 'onMqttReConnect'];
$this->client->onMessage = [$this, 'onMqttMessage'];
$this->client->onError = [$this, 'onMqttError'];
$this->client->connect();
}
nodejs mqtt 接入代码
const mqtt = require('mqtt')
/*
choose which protocol to use for connection here
*/
// const { connectOptions } = require('./use_mqtt.js')
const { connectOptions } = require('./use_mqtts.js')
// const { connectOptions } = require('./use_ws.js')
// const { connectOptions } = require('./use_wss.js')
/**
* this demo uses EMQX Public MQTT Broker (https://www.emqx.com/en/mqtt/public-mqtt5-broker), here are the details:
*
* Broker host: broker.emqx.io
* TCP Port: 1883
* SSL/TLS Port: 8883
* WebSocket port: 8083
* WebSocket over TLS/SSL port: 8084
*/
const clientId = 'emqx_nodejs_' + Math.random().toString(16).substring(2, 8)
const options = {
clientId,
clean: true,
connectTimeout: 4000,
/**
* By default, EMQX allows clients to connect without authentication.
* https://docs.emqx.com/en/enterprise/v4.4/advanced/auth.html#anonymous-login
*/
username: 'emqx_test',
password: 'emqx_test',
reconnectPeriod: 1000,
// Enable the SSL/TLS, whether a client verifies the server's certificate chain and host name
rejectUnauthorized: true,
// for more options and details, please refer to https://github.com/mqttjs/MQTT.js#mqttclientstreambuilder-options
}
const { protocol, host, port } = connectOptions
/**
* if protocol is "mqtt", connectUrl = "mqtt://broker.emqx.io:1883"
* if protocol is "mqtts", connectUrl = "mqtts://broker.emqx.io:8883"
* if protocol is "ws", connectUrl = "ws://broker.emqx.io:8083/mqtt"
* if protocol is "wss", connectUrl = "wss://broker.emqx.io:8084/mqtt"
*
* for more details about "mqtt.connect" method & options,
* please refer to https://github.com/mqttjs/MQTT.js#mqttconnecturl-options
*/
let connectUrl = `${protocol}://${host}:${port}`
if (['ws', 'wss'].includes(protocol)) {
// mqtt: MQTT-WebSocket uniformly uses /path as the connection path,
// which should be specified when connecting, and the path used on EMQX is /mqtt.
connectUrl += '/mqtt'
}
/**
* If you are using mutual (two-way) TLS authentication, you need to pass the CA, client certificate, and client private key.
*/
if (['mqtts', 'wss'].includes(protocol)) {
options['ca'] = fs.readFileSync('./path/to/your/ca.crt')
options['key'] = fs.readFileSync('./path/to/your/client.key')
options['cert'] = fs.readFileSync('./path/to/your/client.crt')
}
const client = mqtt.connect(connectUrl, options)
const topic = '/nodejs/mqtt'
const payload = 'nodejs mqtt test'
// https://github.com/mqttjs/MQTT.js#qos
const qos = 0
// https://github.com/mqttjs/MQTT.js#event-connect
client.on('connect', () => {
console.log(`${protocol}: Connected`)
// subscribe topic
// https://github.com/mqttjs/MQTT.js#mqttclientsubscribetopictopic-arraytopic-object-options-callback
client.subscribe(topic, { qos }, (error) => {
if (error) {
console.log('subscribe error:', error)
return
}
console.log(`${protocol}: Subscribe to topic '${topic}'`)
// publish message
// https://github.com/mqttjs/MQTT.js#mqttclientpublishtopic-message-options-callback
client.publish(topic, payload, { qos }, (error) => {
if (error) {
console.error(error)
}
})
})
})
// https://github.com/mqttjs/MQTT.js#event-reconnect
client.on('reconnect', (error) => {
console.log(`Reconnecting(${protocol}):`, error)
})
// https://github.com/mqttjs/MQTT.js#event-error
client.on('error', (error) => {
console.log(`Cannot connect(${protocol}):`, error)
})
// https://github.com/mqttjs/MQTT.js#event-message
client.on('message', (topic, payload) => {
console.log('Received Message:', topic, payload.toString())
})