使用X.509证书认证并连接设备
分类: Azure物联网 ◆ 标签: #Azure #IoT Hub # #指南 #入门 ◆ 发布于: 2023-06-13 22:01:53
前面有一篇讨论过如何使用自签发证书认证并连接设备,自签发证书实际是上使用证书的指纹进行认证,我们需要将自签发证书的指纹填入设备上,这种形式很复杂,而且也不推荐使用在产线上,如果要用在产线上我们推荐使用有CA签发的证书或者物联网系统用在系统中的话,可以考虑自己作为CA给自己签发证书。
使用CA签发的证书和使用自己作为CA签发的证书,在使用上没有什么区别,都需要:
- 上传用于签发设备的证书,并验证该证书
- 给设备签发设备证书,设备使用该证书认证并连接到
Azure IoT Hub。
本节为了演示,使用openssl让用户成为CA, 并给自己签发证书,然后使用这个签发之后的证书签发设备证书。
使用OpenSSL创建测试证书
如果你是使用Windows, 建议启用WSL, 并安装Linux发行版,同时安装openss。
启动Bash之后,创建目录:
mkdir x509 cd x509 mkdir rootca cd rootca mkdir certs db private touch db/index openssl rand -hex 16 > db/serial echo 1001 > db/crlnumber
并该目录下创建rootca的配置文件rootca.conf, 文件内容如下:
[default]
name = rootca
domain_suffix = example.com
aia_url = http://$name.$domain_suffix/$name.crt
crl_url = http://$name.$domain_suffix/$name.crl
default_ca = ca_default
name_opt = utf8,esc_ctrl,multiline,lname,align
[ca_dn]
commonName = "Test Root CA"
[ca_default]
home = ../rootca
database = $home/db/index
serial = $home/db/serial
crlnumber = $home/db/crlnumber
certificate = $home/$name.crt
private_key = $home/private/$name.key
RANDFILE = $home/private/random
new_certs_dir = $home/certs
unique_subject = no
copy_extensions = none
default_days = 3650
default_crl_days = 365
default_md = sha256
policy = policy_c_o_match
[policy_c_o_match]
countryName = optional
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[req]
default_bits = 2048
encrypt_key = yes
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = ca_dn
req_extensions = ca_ext
[ca_ext]
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
[sub_ca_ext]
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:true,pathlen:0
extendedKeyUsage = clientAuth,serverAuth
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
[client_ext]
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:false
extendedKeyUsage = clientAuth
keyUsage = critical,digitalSignature
subjectKeyIdentifier = hash
然后创建rootca:
openssl req -new -config rootca.conf -out rootca.csr -keyout private/rootca.key
需要注意的是:

这里需要设定rootca的key, 1处和2处设定密码(同样的密码)
然后签发证书:
openssl ca -selfsign -config rootca.conf -in rootca.csr -out rootca.crt -extensions ca_ext
为了演示使用多个证书签发,例如根证书签发子证书,子证书签发设备证书这个过程,我们在有了根证书之后,仍然创建一个子证书,在打开的bash里退回到目录x509, 和rootca同一个根目录下创建:
mkdir subca cd subca mkdir certs db private touch db/index openssl rand -hex 16 > db/serial echo 1001 > db/crlnumber
注意
目录subca和rootca都是在目录x509下,他们是同一个根目录下:
ghw@HongWei-PC:~/x509$ ls
rootca subca
在目录subca下创建子证书的配置subca.conf, 文件内容如下:
[default]
name = subca
domain_suffix = example.com
aia_url = http://$name.$domain_suffix/$name.crt
crl_url = http://$name.$domain_suffix/$name.crl
default_ca = ca_default
name_opt = utf8,esc_ctrl,multiline,lname,align
[ca_dn]
commonName = "Test Subordinate CA"
[ca_default]
home = .
database = $home/db/index
serial = $home/db/serial
crlnumber = $home/db/crlnumber
certificate = $home/$name.crt
private_key = $home/private/$name.key
RANDFILE = $home/private/random
new_certs_dir = $home/certs
unique_subject = no
copy_extensions = copy
default_days = 365
default_crl_days = 90
default_md = sha256
policy = policy_c_o_match
[policy_c_o_match]
countryName = optional
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[req]
default_bits = 2048
encrypt_key = yes
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = ca_dn
req_extensions = ca_ext
[ca_ext]
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
[sub_ca_ext]
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:true,pathlen:0
extendedKeyUsage = clientAuth,serverAuth
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
[client_ext]
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:false
extendedKeyUsage = clientAuth
keyUsage = critical,digitalSignature
subjectKeyIdentifier = hash
生成CSR:
openssl req -new -config subca.conf -out subca.csr -keyout private/subca.key
这里同样要记得需要输入子证书的密码:

使用根证书签发子证书:
openssl ca -config ../rootca/rootca.conf -in subca.csr -out subca.crt -extensions sub_ca_ext
这里因为是用根证书签发,所以记得输入根证书的Key。
现在我们证书已经有了,我们需要将证书上传到Azure IoT Hub,并验证证书。
你可以选择使用根证书,也可以选用这个子证书,为了演示,我们选用子证书,由于Azure IoT Hub支持PEM格式,我们将证书转一下格式:
openssl x509 -in subca.crt -out subca.pem -outform PEM
保存好subca.pem。
验证证书
使用Azure Portal -> Security settings -> Certificates:



点击Generate verification Code之后,会在Verification Code的框中生成一串Code,将这串Code复制保存下来。
回到WSL的subca目录,使用子证书签发一个刚刚的code的证书:
openssl genpkey -out pop.key -algorithm RSA -pkeyopt rsa_keygen_bits:2048 openssl req -new -key pop.key -out pop.csr ----- Country Name (2 letter code) [XX]:. State or Province Name (full name) []:. Locality Name (eg, city) [Default City]:. Organization Name (eg, company) [Default Company Ltd]:. Organizational Unit Name (eg, section) []:. Common Name (eg, your name or your server hostname) []:{你刚刚复制保存的`code`} Email Address []: Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []:
这里需要注意的是:Common Name (eg, your name or your server hostname) []: 填充的值是刚刚复制保存的值。
openssl ca -config subca.conf -in pop.csr -out pop.crt -extensions client_ext openssl x509 -in pop.crt -out pop.pem -outform PEM
上述命令会生成证书文件pop.pem,保存好该文件,回到 Azure Portal -> Security settings -> Certificates, 在列表里找到刚刚上传的证书,点击打开,然后上传该证书文件,并点击Verify,即可完成证书的验证。
创建设备并签发设备证书
到了这一步,我们前使用Azure Portal创建一个新的设备testdevice3, 然后使用subca证书给该设备签发一个证书。
创建设备:

注意选择X.509 CA Signed
点击Create即可完成创建了。
回到WSL的subca目录下,开始为设备testdevice3创建设备证书:
openssl genpkey -out testdevice3.key -algorithm RSA -pkeyopt rsa_keygen_bits:2048 openssl req -new -key testdevice3.key -out testdevice3.csr
创建CSR时:

Common Name (eg, your name or your server hostname) []:的值是设备的ID: testdevice3。
openssl ca -config subca.conf -in testdevice3.csr -out testdevice3.crt -extensions client_ext openssl pkcs12 -export -in testdevice3.crt -inkey testdevice3.key -out testdevice3.pfx
生成了证书文件testdevice3.pfx。
测试代码
本章的测试代码和之前的自签发证书的代码实际上是一摸一样的。
注意
本节的代码可以从:https://github.com/hylinux/azure-iot-hub-examples/tree/main/DeviceConnectByX509 下载。
dotnet new console -o DeviceConnectByX509 cd .\DeviceConnectByX509\ dotnet add package Microsoft.Extensions.Hosting dotnet add package Microsoft.Azure.Devices.Client mkdir X509 cp {yourpath}\testdevice3.pfx .\X509\
将之前创建好的pfx证书拷贝到目录X509里。需要注意的时,同一时间只能使用一个证书。
我们在本项目中仍然使用Secret Manager来管理必要的机密信息:
dotnet user-secrets init dotnet user-secrets set "Device:IoTHubHostURL" "<你IoT Hub服务器的网址>" dotnet user-secrets set "X509:Password" "<你证书的密码>" dotnet user-secrets set "Device:Id" "testdevice2"
打开文件Program.cs,添加如下的内容:
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Azure.Devices.Client; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.Json; using IHost host = Host.CreateDefaultBuilder(args).Build(); var iotHubHostURL = host.Services.GetRequiredService<IConfiguration>().GetValue<string>("Device:IoTHubHostURL"); var certPassword = host.Services.GetRequiredService<IConfiguration>().GetValue<string>("X509:Password"); var deviceId = host.Services.GetRequiredService<IConfiguration>().GetValue<string>("Device:Id"); var cert = new X509Certificate2(@"D:\MyProjects\azure-iot-hub-examples\DeviceConnectByX509\X509\testdevice3.pfx", certPassword); var auth = new DeviceAuthenticationWithX509Certificate(deviceId, cert); var deviceClient = DeviceClient.Create(iotHubHostURL, auth, TransportType.Mqtt); Console.WriteLine("设备连接正常。"); await host.RunAsync();
从上述代码中可以看出要使用自签发证书主要这三行代码在起作用:
var cert = new X509Certificate2(@"D:\MyProjects\azure-iot-hub-examples\DeviceConnectByX509\X509\testdevice3.pfx", certPassword); var auth = new DeviceAuthenticationWithX509Certificate(deviceId, cert); var deviceClient = DeviceClient.Create(iotHubHostURL, auth, TransportType.Mqtt);
在底部加上方法SendDeviceToCloudMessagesAsync的定义,并在Console.WriteLine("设备连接正常。");后面加上以下两行:
using var cts = new CancellationTokenSource(); await SendDeviceToCloudMessagesAsync(deviceClient, cts.Token);
运行应用:
$env:DOTNET_ENVIRONMENT = "Development" dotnet run
即可以观察连接的结果。