Skip to main content

使用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

注意
目录subcarootca都是在目录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复制保存下来。

回到WSLsubca目录,使用子证书签发一个刚刚的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即可完成创建了。

回到WSLsubca目录下,开始为设备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) []:的值是设备的IDtestdevice3

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

即可以观察连接的结果。