使用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
即可以观察连接的结果。