Skip to main content
Version: 3.2.1

使用X.509证书认证

使用前须知: 生成X509证书的方案有两种,分别为使用openssl使用shell脚本生成nopass文件,这边强烈建议您使用第一种方案(使用openssl生成),第二种方案(使用shell脚本生成nopass文件)已被弃用!

生成X.509证书(使用openssl)

找一台Linux主机执行以下命令

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -nodes

此操作将会产生两个文件cert.pemkey.pem

创建设备

详见创建设备章节,这里需要超链接到创建设备章节

在设备的管理认证界面将认证方式选择为MQTT X.509证书,并将cert.pem的内容完全复制到RSA公钥文本框。如下图所示:

连接平台并上传数据

上传数据之前,确保当前目录拥有三个文件(cert.pemkey.pemmqttserver.pub.pem),前两个文件可以通过本文开头的生成X509证书生成,最后一个服务器公钥mqttserver.pub.pem请联系管理员,使用以下代码即可上传数据

import ssl
import json

import paho.mqtt.client as mqtt

things_host = "things.xiaobodata.com"

def things_on_connect(client, userdata, flags, rc):
print("things_on_connect userdata:%s rc:%s", userdata, rc)
# 这是要上传的数据
data = {'key': 'v1'}
client.publish("v1/devices/me/telemetry", json.dumps(data), 1)


def things_on_disconnect(client, userdata, rc):
print("things_on_disconnect userdata:%s rc:%s", userdata, rc)


def things_on_message(client, userdata, msg):
print("things_on_message userdata:%", userdata)


if __name__ == '__main__':
# 连接things mqtt
things_mqtt = mqtt.Client(clean_session=True)
things_mqtt.on_disconnect = things_on_disconnect
things_mqtt.on_message = things_on_message
things_mqtt.on_connect = things_on_connect
things_mqtt.tls_set(ca_certs="mqttserver.pub.pem", certfile="cert.pem", keyfile="key.pem",
tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)
things_mqtt.tls_insecure_set(False)
things_mqtt.connect(host=things_host, port=8883)
things_mqtt.loop_forever()

在平台的设备遥测数据中看到上传的数据即代表成功

使用nopass文件(使用shell脚本生成nopass文件)(已弃用!)

在Linux新建一个空白目录并将以下两个文件放入该目录下

client.keygen.sh文件的内容如下:

#!/bin/bash
#
# Copyright © 2016-2021 The WaveletThings
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

usage() {
echo "This script generates client public/private key pair, extracts them to a no-password pem file,"
echo "and imports server public key to client keystore"
echo "usage: ./client.keygen.sh [-p file]"
echo " -p | --props | --properties file Properties file. default value is ./keygen.properties"
echo " -h | --help | ? Show this message"
}

PROPERTIES_FILE=keygen.properties

while true; do
case "$1" in
-p | --props | --properties) PROPERTIES_FILE=$2 ;
shift
;;
-h | --help | ?) usage
exit 0
;;
-- ) shift;
break
;;
* ) break
;;
esac
shift
done

. $PROPERTIES_FILE

if [ -f $CLIENT_FILE_PREFIX.jks ] || [ -f $CLIENT_FILE_PREFIX.pub.pem ] || [ -f $CLIENT_FILE_PREFIX.nopass.pem ] || \
[ -f $CLIENT_FILE_PREFIX.pem ] || [ -f $CLIENT_FILE_PREFIX.p12 ] || [ -f $CLIENT_FILE_PREFIX.pk8.pem ];
then
while :
do
read -p "Output files from previous server.keygen.sh script run found. Overwrite? [Y/N]: " response
case $response in
[nN]|[nN][oO])
echo "Skipping"
echo "Done"
exit 0
;;
[yY]|[yY][eE]|[yY][eE][sS]|"")
echo "Cleaning up files"
rm -rf $CLIENT_FILE_PREFIX.jks
rm -rf $CLIENT_FILE_PREFIX.pub.pem
rm -rf $CLIENT_FILE_PREFIX.nopass.pem
rm -rf $CLIENT_FILE_PREFIX.pem
rm -rf $CLIENT_FILE_PREFIX.p12
rm -rf $CLIENT_FILE_PREFIX.pk8.pem
break;
;;
*) echo "Please reply 'yes' or 'no'"
;;
esac
done
fi

OPENSSL_CMD=""
case $CLIENT_KEY_ALG in
RSA)
OPENSSL_CMD="rsa"
;;
EC)
OPENSSL_CMD="ec"
;;
esac
if [ -z "$OPENSSL_CMD" ]; then
echo "Unexpected CLIENT_KEY_ALG. Exiting."
exit 0
fi

echo "INFO: your hostname is $(hostname)"
echo "INFO: your CN (domain suffix) for key is $DOMAIN_SUFFIX"
echo "Generating SSL Key Pair..."

keytool -genkeypair -v \
-alias $CLIENT_KEY_ALIAS \
-keystore $CLIENT_FILE_PREFIX.jks \
-keypass $CLIENT_KEY_PASSWORD \
-storepass $CLIENT_KEYSTORE_PASSWORD \
-keyalg $CLIENT_KEY_ALG \
-keysize $CLIENT_KEY_SIZE\
-validity 9999 \
-dname "CN=$DOMAIN_SUFFIX, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE"

echo "Converting keystore to pkcs12"
keytool -importkeystore \
-srckeystore $CLIENT_FILE_PREFIX.jks \
-destkeystore $CLIENT_FILE_PREFIX.p12 \
-srcalias $CLIENT_KEY_ALIAS \
-srcstoretype jks \
-deststoretype pkcs12 \
-srcstorepass $CLIENT_KEYSTORE_PASSWORD \
-deststorepass $CLIENT_KEY_PASSWORD \
-srckeypass $CLIENT_KEY_PASSWORD \
-destkeypass $CLIENT_KEY_PASSWORD

echo "Converting pkcs12 to pem"
openssl pkcs12 -in $CLIENT_FILE_PREFIX.p12 \
-out $CLIENT_FILE_PREFIX.pem \
-passin pass:$CLIENT_KEY_PASSWORD \
-passout pass:$CLIENT_KEY_PASSWORD

echo "Converting pem to pkcs8"
openssl pkcs8 \
-topk8 \
-nocrypt \
-in $CLIENT_FILE_PREFIX.pem \
-out $CLIENT_FILE_PREFIX.pk8.pem \
-passin pass:$CLIENT_KEY_PASSWORD

echo "Importing server public key to $CLIENT_FILE_PREFIX.jks"
keytool --importcert \
-file $SERVER_FILE_PREFIX.cer \
-keystore $CLIENT_FILE_PREFIX.jks \
-alias $SERVER_KEY_ALIAS \
-keypass $SERVER_KEY_PASSWORD \
-storepass $CLIENT_KEYSTORE_PASSWORD \
-noprompt

echo "Exporting no-password pem certificate"
openssl $OPENSSL_CMD -in $CLIENT_FILE_PREFIX.pem -out $CLIENT_FILE_PREFIX.nopass.pem -passin pass:$CLIENT_KEY_PASSWORD
tail -n +$(($(grep -m1 -n -e '-----BEGIN CERTIFICATE' $CLIENT_FILE_PREFIX.pem | cut -d: -f1) )) \
$CLIENT_FILE_PREFIX.pem >> $CLIENT_FILE_PREFIX.nopass.pem

echo "Exporting client public key"
tail -n +$(($(grep -m1 -n -e '-----BEGIN CERTIFICATE' $CLIENT_FILE_PREFIX.pem | cut -d: -f1) )) \
$CLIENT_FILE_PREFIX.pem >> $CLIENT_FILE_PREFIX.pub.pem

echo "Done."

keygen.properties文件的内容如下:

DOMAIN_SUFFIX="localhost"
ORGANIZATIONAL_UNIT=WaveletThings
ORGANIZATION=WaveletThings
CITY="Tianjin"
STATE_OR_PROVINCE=CA
TWO_LETTER_COUNTRY_CODE=CN

SERVER_KEYSTORE_PASSWORD=server_ks_password
SERVER_KEY_PASSWORD=server_key_password

SERVER_KEY_ALIAS="serveralias"
SERVER_FILE_PREFIX="mqttserver"
SERVER_KEYSTORE_DIR="/etc/thingsboard/conf/"

CLIENT_KEYSTORE_PASSWORD=password
CLIENT_KEY_PASSWORD=password

CLIENT_KEY_ALIAS="clientalias"
CLIENT_FILE_PREFIX="mqttclient"
CLIENT_KEY_ALG="RSA"
CLIENT_KEY_SIZE="2048"
KEY解释
DOMAIN_SUFFIX对应于证书的CN值。必须对应于目标服务器域(允许使用通配符)。默认为当前主机名
ORGANIZATIONAL_UNIT对应于证书的组织单位。
ORGANIZATION对应于证书的组织。
CITY城市
STATE_OR_PROVINCE国家或省份名称
TWO_LETTER_COUNTRY_CODE两个字母的国家代码
SERVER_KEYSTORE_PASSWORD服务器密钥库密码
SERVER_KEY_PASSWORD服务器密钥密码。可能与服务器密钥库密码相同,也可能与服务器密钥库密码不同
SERVER_KEY_ALIAS服务器密钥别名。在密钥库中必须是唯一的
SERVER_FILE_PREFIX所有与服务器密钥生成相关的输出文件的前缀
SERVER_KEYSTORE_DIR可以选择复制密钥的默认位置。

要运行服务器密钥库生成,请使用以下命令:

chmod +x server.keygen.sh
sudo ./server.keygen.sh

执行成功后当前目录会有这些文件:

mqttclient.pub.pem文件中的内容复制到RSA公钥中:

使用下面的Python代码将数据上传,上传数据之前,确保当前目录拥有mqttserver.pub.pem文件和mqttclient.nopass.pem文件,mqttclient.nopass.pem文件为脚本生成,mqttserver.pub.pem请联系管理员,使用以下代码即可上传数据

import ssl
import json

import paho.mqtt.client as mqtt

things_host = "things.xiaobodata.com"


def things_on_connect(client, userdata, flags, rc):
print("things_on_connect userdata:%s rc:%s", userdata, rc)
data = {"key": "v2"}
client.publish("v1/devices/me/telemetry", json.dumps(data), 1)


def things_on_disconnect(client, userdata, rc):
print("things_on_disconnect userdata:%s rc:%s", userdata, rc)


def things_on_message(client, userdata, msg):
print("things_on_message userdata:%", userdata)


if __name__ == '__main__':
# 连接things mqtt
things_mqtt = mqtt.Client(clean_session=True)
things_mqtt.on_disconnect = things_on_disconnect
things_mqtt.on_message = things_on_message
things_mqtt.on_connect = things_on_connect
things_mqtt.tls_set(ca_certs="mqttserver.pub.pem", certfile="mqttclient.nopass.pem",
tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)
things_mqtt.tls_insecure_set(False)
things_mqtt.connect(host=things_host, port=8883)
things_mqtt.loop_forever()

在平台的设备遥测数据中看到上传的数据即代表成功