<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>MTLS on Yuanye Ma's Blog</title><link>/tags/mtls/</link><description>Recent content in MTLS on Yuanye Ma's Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>yuanye.ma@qq.com (Yuanye Ma)</managingEditor><webMaster>yuanye.ma@qq.com (Yuanye Ma)</webMaster><lastBuildDate>Sat, 25 Jul 2026 10:00:00 +0800</lastBuildDate><atom:link href="/tags/mtls/index.xml" rel="self" type="application/rss+xml"/><item><title>非对称加密与 mTLS 身份认证</title><link>/self-hosting/2026-07-25-mtls-certificate/</link><pubDate>Sat, 25 Jul 2026 10:00:00 +0800</pubDate><author>yuanye.ma@qq.com (Yuanye Ma)</author><guid>/self-hosting/2026-07-25-mtls-certificate/</guid><description>&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;术语&lt;/th&gt;
&lt;th&gt;解释&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;无状态的超文本传输协议，明文传输&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSL&lt;/td&gt;
&lt;td&gt;使用加密技术保护数据安全性，安全性被质疑，现在逐步被 TLS 取代&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS&lt;/td&gt;
&lt;td&gt;传输层安全性，SSL 的后续版本，提供比 SSL 更强的加密和身份验证。有多个版本 TLS 1.0/1.1/1.2/1.3 等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;HTTP 的安全版本，使用 SSL/TLS 协议在 HTTP 的基础上提供加密和安全性。双方先用非对称加密协商出密钥，后续通信使用对称加密&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;X.509&lt;/td&gt;
&lt;td&gt;定义了数字证书的格式标准和使用规范&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="加解密过程"&gt;加/解密过程&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-mermaid" data-lang="mermaid"&gt;sequenceDiagram
participant Bob
participant Alice
Alice -&amp;gt;&amp;gt; Bob : Alice的公钥公开
Note right of Bob : 明文 XXX
Note right of Bob : 用Alice的公钥对明文加密
Note right of Bob : 得到密文 YYYY
Bob -&amp;gt;&amp;gt; Alice : 密文
Note right of Alice: 用Alice的私钥对密文进行解密
Note right of Alice : 得到解密后的明文 XXX
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;中间的密文，如果没有私钥，就没办法进行破解&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="签名验签过程"&gt;签名/验签过程&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-mermaid" data-lang="mermaid"&gt;graph LR
subgraph 发送方
direction TB
A[原始信息]
B[Hash运算]
C[&amp;#34;摘要（Digest）&amp;#34;]
D[&amp;#34;签名（Signature）&amp;#34;]
A --&amp;gt;|MD5/SHA-256| B
B --&amp;gt; C
C --&amp;gt;|用私钥加密| D
end
subgraph 接收方
direction TB
E[原始信息]
F[Hash运算]
G[&amp;#34;摘要（Digest）&amp;#34;]
G&amp;#39;[&amp;#34;摘要&amp;#39;（Digest&amp;#39;）&amp;#34;]
H[&amp;#34;签名（Signature）&amp;#34;]
I{判断是否相等}
E --&amp;gt;|MD5/SHA-256| F
F --&amp;gt; G
H --&amp;gt;|用公钥解密| G&amp;#39;
G --&amp;gt; I
G&amp;#39; --&amp;gt; I
end
D --&amp;gt;|发送含签名的信息| H
A -.-&amp;gt;|原始信息也发送| E
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;a. 如果信息原文被篡改，则HASH后的摘要就会变，Digest和Digest&amp;rsquo;就不一样了，验签失败。
b. 如果签名被替换了的话，则公钥解密失败（解密出来的东西就不一样了），Digest和Digest&amp;rsquo;就不一样了，验签失败。&lt;/p&gt;
&lt;p&gt;签名证明了三件事&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不可否认性 ： 只有持有私钥的人能产生这个签名，赖不掉。&lt;/li&gt;
&lt;li&gt;完整性 ： 签名包含了消息的hash，消息被篡改一个字节，hash就对不上了。&lt;/li&gt;
&lt;li&gt;不可伪造性 ： 没有私钥的人，无法为任意消息算出有效的签名&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;引入CA&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;发送方将自己的个人信息以及公钥，用CA的私钥进行签名，得到证书。&lt;/li&gt;
&lt;li&gt;发送方将需要发送的原始信息和证书以及个人签名一起发送。&lt;/li&gt;
&lt;li&gt;接受方，收到信息后，
&lt;ol&gt;
&lt;li&gt;用CA的公钥验证发送发的证书，验证通过（证书确实是发送方的证书，并且经过CA签名的，未经篡改，证书中的公钥确实是发送发的公钥）；&lt;/li&gt;
&lt;li&gt;拿到发送方的公钥（从证书中提取）&lt;/li&gt;
&lt;li&gt;用提取得到的公钥验证发送方附带的个人签名，确认确实是发送方发送的。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="证书"&gt;证书&lt;/h2&gt;
&lt;h3 id="证书结构"&gt;证书结构&lt;/h3&gt;
&lt;p&gt;X.509证书结构&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-7"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-8"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-9"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-10"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-11"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-12"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-12"&gt;12&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-13"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-13"&gt;13&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-14"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-14"&gt;14&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-15"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-15"&gt;15&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-16"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-16"&gt;16&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-17"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-17"&gt;17&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-18"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-18"&gt;18&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-19"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-19"&gt;19&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-20"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-20"&gt;20&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-21"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-21"&gt;21&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;版本：V3
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;序列号：12:34:56:78 （唯一序列号）
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;签名算法：ECDSA-SHA
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;签发者 ：
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; C =CN，
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; O=domain-name,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CN = domain-name Factory CA
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;有效期
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Not Befor : 2026-07-24
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Not After:2036-07-21
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;主题（Subject）
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CN = DEV-20260725-00001
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; O = domain-name
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;持有人公钥：
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Algorithm : EC P-256 (prime256v1)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Key : 04:0a:9f:22:b4:26:...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;签名 ： 签名者用私钥对以上信息进行签名(HASH取摘要，对摘要用私钥进行加密)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;比如以下这个设备证书&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-7"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-8"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-9"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-10"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-11"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-12"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-12"&gt;12&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-13"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-13"&gt;13&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-14"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-14"&gt;14&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-15"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-15"&gt;15&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-16"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-16"&gt;16&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-17"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-17"&gt;17&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-18"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-18"&gt;18&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-19"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-19"&gt;19&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-20"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-20"&gt;20&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-21"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-21"&gt;21&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-22"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-22"&gt;22&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-23"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-23"&gt;23&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-24"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-24"&gt;24&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-25"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-25"&gt;25&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-26"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-26"&gt;26&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-27"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-27"&gt;27&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-28"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-28"&gt;28&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-29"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-29"&gt;29&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-30"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-30"&gt;30&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-31"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-31"&gt;31&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-32"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-32"&gt;32&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-33"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-33"&gt;33&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-34"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-34"&gt;34&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# openssl x509 -in ./device.crt -text -noout
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Certificate:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Data:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Version: 3 (0x2)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Serial Number:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 52:23:64:76:be:87:9c:90:2a:7a:a8:13:25:5b:ef:4e:8e:04:d8:02
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Signature Algorithm: ecdsa-with-SHA256
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Issuer: C=CN, O=domain-name, CN=domain-name Factory CA
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Validity
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Not Before: Jul 24 14:55:48 2026 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Not After : Jul 21 14:55:48 2036 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Subject: CN=DEV-20260718-00001, O=domain-name
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Subject Public Key Info:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Public Key Algorithm: id-ecPublicKey
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Public-Key: (256 bit)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pub:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 04:0a:9f:22:b4:26:cc:ed:6e:c4:6c:5f:67:c4:34:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; b1:86:53:ab:b1:26:9c:fd:9b:aa:54:8c:ad:d3:10:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; a3:0c:de:a7:f9:d5:c9:66:cb:ec:59:67:a0:01:f2:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 58:6b:b4:03:e8:ce:ce:13:05:90:4b:45:ce:07:b3:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 56:3d:e3:8d:cd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ASN1 OID: prime256v1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NIST CURVE: P-256
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; X509v3 extensions:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; X509v3 Subject Key Identifier:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AF:7C:A8:CE:D6:C6:D9:A7:E6:18:51:8F:7E:0F:3A:F5:BD:6B:9C:99
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; X509v3 Authority Key Identifier:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; EF:7B:9C:DA:90:EB:8E:1A:87:2F:61:34:C2:4F:8A:30:5D:E0:E7:CE
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Signature Algorithm: ecdsa-with-SHA256
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Signature Value:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 30:45:02:20:78:ea:6c:67:20:f5:4a:b2:2c:0e:1e:85:4c:f6:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ab:16:a0:e4:58:14:9b:30:c8:e0:3d:22:28:c5:3e:54:53:18:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 02:21:00:b8:81:b0:19:c7:05:e5:eb:d4:82:a7:af:28:b3:d5:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ff:16:61:1b:64:30:a3:f0:27:33:ca:7c:58:8e:fe:db:77
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;证书 = 公钥 + 身份 + CA签名&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;编号&lt;/th&gt;
&lt;th&gt;全程&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CN&lt;/td&gt;
&lt;td&gt;Common Name&lt;/td&gt;
&lt;td&gt;实体的名字&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;Organization&lt;/td&gt;
&lt;td&gt;组织/公司名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;Country&lt;/td&gt;
&lt;td&gt;国家代码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="证书生成命令"&gt;证书生成命令&lt;/h3&gt;
&lt;p&gt;生成证书的过程：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-7"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-8"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-9"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-10"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-11"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-12"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-12"&gt;12&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-13"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-13"&gt;13&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;# 1. 生成设备 EC P-256 密钥对&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssl ecparam -genkey -name prime256v1 -out device.key
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;# 2. 生成 CSR (证书签名请求)，CN 填设备 SN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssl req -new -key device.key -out device.csr -subj &lt;span style="color:#4070a0"&gt;&amp;#34;/CN=DEV-20260718-00001/O=domain-name&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;# 3. 工厂 CA 签发证书（10 年）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssl x509 -req -days &lt;span style="color:#40a070"&gt;3650&lt;/span&gt; -in device.csr &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -CA factory-ca.crt -CAkey factory-ca.key &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -sha256 -out device.crt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;# 4. 构建证书链（设备 + 中间 CA）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cat device.crt factory-ca.crt &amp;gt; chain.crt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;查看证书中的信息：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-7"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-8"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-9"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-10"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-11"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-12"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-12"&gt;12&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-13"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-13"&gt;13&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-14"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-14"&gt;14&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;# 基本信息（主题、签发者、有效期）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssl x509 -in device.crt -noout -subject -issuer -dates
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;# 指纹&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssl x509 -in device.crt -noout -fingerprint -sha256
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;# 完整内容&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssl x509 -in device.crt -text -noout
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;# 验证证书链&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssl verify -CAfile root-ca.crt -untrusted factory-ca.crt device.crt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;# 查看私钥&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssl ec -in device.key -text -noout
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="证书链"&gt;证书链&lt;/h3&gt;
&lt;p&gt;生成顺序：Root → Factory → Device
信任方向：Device → Factory → Root (反过来验证)&lt;/p&gt;
&lt;p&gt;第 1 步：生成 Root CA（自签名）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-5"&gt;5&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-6"&gt;6&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;# 生成根私钥&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssl ecparam -genkey -name prime256v1 -out root-ca.key
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;# 自己给自己签发&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssl req -new -x509 -days &lt;span style="color:#40a070"&gt;10950&lt;/span&gt; -key root-ca.key -out root-ca.crt &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -subj &lt;span style="color:#4070a0"&gt;&amp;#34;/C=CN/O=domain-name/CN=domain-name Root CA&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;产物：root-ca.key + root-ca.crt。
root-ca.crt 里 Issuer = Subject = domain-name Root CA（自己签自己）。&lt;/p&gt;
&lt;p&gt;第 2 步：生成 Factory CA（由 Root 签发）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-7"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-8"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-9"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-10"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-11"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;# 生成工厂私钥&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl ecparam -genkey -name prime256v1 -out factory-ca.key
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;# 生成 CSR (证书请求文件)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl req -new -key factory-ca.key -out factory-ca.csr &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -subj &lt;span style="color:#4070a0"&gt;&amp;#34;/C=CN/O=domain-name/CN=domain-name Factory CA&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;# Root 用自己的私钥签发 Factory 证书&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl x509 -req -days &lt;span style="color:#40a070"&gt;7300&lt;/span&gt; -in factory-ca.csr &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -CA root-ca.crt -CAkey root-ca.key &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -sha256 -out factory-ca.crt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;此时 factory-ca.crt 里 Issuer = Root CA, Subject = Factory CA。
Root 用自己的私钥在 Factory 证书上签了名。&lt;/p&gt;
&lt;p&gt;第 3 步：生成 Device 证书（由 Factory 签发）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-1"&gt; 1&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-2"&gt; 2&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-3"&gt; 3&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-4"&gt; 4&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-5"&gt; 5&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-6"&gt; 6&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-7"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-7"&gt; 7&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-8"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-8"&gt; 8&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-9"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-9"&gt; 9&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-10"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-10"&gt;10&lt;/a&gt;
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-11"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-11"&gt;11&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;# 生成设备私钥&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl ecparam -genkey -name prime256v1 -out device.key
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;# 生成 CSR（CN = 设备 SN，这就是设备身份）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl req -new -key device.key -out device.csr &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -subj &lt;span style="color:#4070a0"&gt;&amp;#34;/CN=DEV-20260718-00001/O=domain-name&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;# Factory 用自己的私钥签发设备证书&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl x509 -req -days &lt;span style="color:#40a070"&gt;3650&lt;/span&gt; -in device.csr &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -CA factory-ca.crt -CAkey factory-ca.key &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -sha256 -out device.crt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;此时 device.crt 里 Issuer = Factory CA, Subject = CN=DEV-20260718-00001。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-mermaid" data-lang="mermaid"&gt;
graph BT
subgraph &amp;#34;证书链（自上而下签发）&amp;#34;
Root[&amp;#34;Root CA&amp;lt;br/&amp;gt;自签名&amp;lt;br/&amp;gt;Issuer=Root CA&amp;lt;br/&amp;gt;Subject=Root CA&amp;lt;br/&amp;gt;用 root-ca.key&amp;#34;]
Factory[&amp;#34;Factory CA&amp;lt;br/&amp;gt;由 Root 签发&amp;lt;br/&amp;gt;Issuer=Root CA&amp;lt;br/&amp;gt;Subject=Factory CA&amp;lt;br/&amp;gt;用 factory-ca.key&amp;#34;]
Device[&amp;#34;Device Cert&amp;lt;br/&amp;gt;由 Factory 签发&amp;lt;br/&amp;gt;Issuer=Factory CA&amp;lt;br/&amp;gt;Subject=CN=DEV-...00001&amp;lt;br/&amp;gt;用 device.key&amp;#34;]
end
Root -- &amp;#34;用 root-ca.key 签名&amp;#34; --&amp;gt; Factory
Factory -- &amp;#34;用 factory-ca.key 签名&amp;#34; --&amp;gt; Device
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;每台设备预置以下信息&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件名&lt;/th&gt;
&lt;th&gt;解释&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;device.key&lt;/td&gt;
&lt;td&gt;设备私钥&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;device.crt&lt;/td&gt;
&lt;td&gt;设备证书&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;chain.crt&lt;/td&gt;
&lt;td&gt;证书链，里边有device.crt和factory-ca.crt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ca.crt&lt;/td&gt;
&lt;td&gt;根证书（用于验证服务器身份）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-mermaid" data-lang="mermaid"&gt;sequenceDiagram
participant Dev as 设备&amp;lt;br/&amp;gt;DEV-20260718-00001&amp;lt;br/&amp;gt;持有: device.key + chain.crt + ca.crt
participant MQTT Broker as MQTT Broker&amp;lt;br/&amp;gt;持有: server.key + server.crt&amp;lt;br/&amp;gt;信任: root-ca.crt
Note over Dev,MQTT Broker: ═══════ 1️⃣ 设备验证服务器 ═══════
Dev-&amp;gt;&amp;gt;MQTT Broker: ClientHello
MQTT Broker--&amp;gt;&amp;gt;Dev: ServerHello + server.crt
Note over Dev: 设备验证服务器身份:&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;第1步: 用 ca.crt 验 server.crt 的签名&amp;lt;br/&amp;gt;openssl verify -CAfile ca.crt server.crt&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;第2步: 检查 server.crt 的 CN 是否 = domain-name.site&amp;lt;br/&amp;gt;(防止中间人用别的合法证书冒充)&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;✅ 签名有效 + CN 匹配 → 服务器身份确认
Note over Dev,MQTT Broker: ═══════ 2️⃣ 服务器验证设备 ═══════
MQTT Broker-&amp;gt;&amp;gt;Dev: CertificateRequest&amp;lt;br/&amp;gt;(请出示你的证书)
Dev--&amp;gt;&amp;gt;MQTT Broker: chain.crt&amp;lt;br/&amp;gt;(device.crt + factory-ca.crt)
Note over MQTT Broker: 服务器验证设备身份:&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;第1步: 构建信任链&amp;lt;br/&amp;gt;device.crt 的 Issuer = Factory CA&amp;lt;br/&amp;gt;→ 用 factory-ca.crt 验 device.crt&amp;lt;br/&amp;gt;→ factory-ca.crt 的 Issuer = Root CA&amp;lt;br/&amp;gt;→ 用 root-ca.crt 验 factory-ca.crt&amp;lt;br/&amp;gt;→ root-ca.crt 在 MQTT Broker 信任库中 ✅&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;第2步: 提取设备身份&amp;lt;br/&amp;gt;CN = DEV-20260718-00001&amp;lt;br/&amp;gt;→ 这就是 MQTT username&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;✅ 证书链完整 → 设备身份确认
Dev-&amp;gt;&amp;gt;MQTT Broker: 密钥交换
MQTT Broker--&amp;gt;&amp;gt;Dev: 加密通道建立 ✅
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="私钥和证书的关系"&gt;私钥和证书的关系&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;私钥和证书是一对的&lt;/li&gt;
&lt;li&gt;私钥：设备自己藏着，绝不出设备，TLS握手时用来签名&lt;/li&gt;
&lt;li&gt;证书：TLS握手是发送给服务器，包含设备公钥、身份、CA签名&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="mtls完整认证流程"&gt;mTLS完整认证流程&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-mermaid" data-lang="mermaid"&gt;sequenceDiagram
participant D as 设备&amp;lt;br/&amp;gt;DEV-20260718-00001
participant E as MQTT Broker&amp;lt;br/&amp;gt;MQTT Broker&amp;lt;br/&amp;gt;:8883 (MQTTS)
participant B as 后端 FastAPI&amp;lt;br/&amp;gt;内部 :8000
participant P as PostgreSQL
Note over D,P: ═══ 阶段一: TLS 双向握手 (mTLS) ═══
D-&amp;gt;&amp;gt;E: ClientHello&amp;lt;br/&amp;gt;TLS 版本 + 支持的密码套件
E--&amp;gt;&amp;gt;D: ServerHello&amp;lt;br/&amp;gt;选 ECDHE-ECDSA-AES256-GCM-SHA384&amp;lt;br/&amp;gt;+ 出示 server.crt&amp;lt;br/&amp;gt;(&amp;#34;CN=domain-name.site&amp;#34;)
Note over D: 验证服务器证书链:&amp;lt;br/&amp;gt;server.crt ← Root CA (ca.crt)&amp;lt;br/&amp;gt;✅ 签名有效, CN 匹配
E-&amp;gt;&amp;gt;D: CertificateRequest&amp;lt;br/&amp;gt;(要求客户端提供证书)
D--&amp;gt;&amp;gt;E: 出示 chain.crt&amp;lt;br/&amp;gt;(device.crt + factory-ca.crt)
Note over E: 验证设备证书链:&amp;lt;br/&amp;gt;device.crt ← Factory CA ← Root CA&amp;lt;br/&amp;gt;✅ 签名有效&amp;lt;br/&amp;gt;提取 CN = &amp;#34;DEV-20260718-00001&amp;#34;&amp;lt;br/&amp;gt;→ 这就是 MQTT username
D--&amp;gt;&amp;gt;E: 密钥交换 + Finished&amp;lt;br/&amp;gt;✅ TLS 加密通道建立
Note over D,P: ═══ 阶段二: MQTT CONNECT ═══
D-&amp;gt;&amp;gt;E: MQTT CONNECT&amp;lt;br/&amp;gt;username=DEV-20260718-00001&amp;lt;br/&amp;gt;password=(空, mTLS 已认证)
Note over E: 提取 username = CN&amp;lt;br/&amp;gt;查询 ACL 规则
E-&amp;gt;&amp;gt;E: ACL 检查&amp;lt;br/&amp;gt;❓ 你能访问哪些主题?
Note over E: ACL 规则:&amp;lt;br/&amp;gt;✅ DEV-* → vending/{username}/#&amp;lt;br/&amp;gt;→ DEV-20260718-00001 只能访问&amp;lt;br/&amp;gt;vending/DEV-20260718-00001/#
E--&amp;gt;&amp;gt;D: CONNACK (连接成功)
Note over D,P: ═══ 阶段三: 设备注册/状态更新 ═══
E-&amp;gt;&amp;gt;B: Webhook: POST /api/v1/mqtt/hooks/connected&amp;lt;br/&amp;gt;{&amp;#34;username&amp;#34;:&amp;#34;DEV-20260718-00001&amp;#34;,&amp;#34;peercert&amp;#34;:{...}}
B-&amp;gt;&amp;gt;P: SELECT * FROM devices WHERE device_sn=&amp;#39;DEV-20260718-00001&amp;#39;
alt 设备不存在 (首次连接)
P--&amp;gt;&amp;gt;B: 无记录
B-&amp;gt;&amp;gt;P: INSERT INTO devices&amp;lt;br/&amp;gt;(device_sn, status=&amp;#39;unregistered&amp;#39;, is_active=true)
B-&amp;gt;&amp;gt;B: 返回 &amp;#34;registered&amp;#34;
else 设备已禁用 (is_active=false)
P--&amp;gt;&amp;gt;B: is_active=false
B-&amp;gt;&amp;gt;E: API: ban_device + disconnect_client
E--&amp;gt;&amp;gt;D: ❌ 断开连接
else 正常设备
P--&amp;gt;&amp;gt;B: is_active=true
B-&amp;gt;&amp;gt;P: UPDATE devices SET last_online_at=now()
B-&amp;gt;&amp;gt;B: 返回 &amp;#34;ok&amp;#34;
end
Note over D,P: ═══ 阶段四: 心跳通信 ═══
loop 每 15 秒
D-&amp;gt;&amp;gt;E: PUBLISH vending/DEV-20260718-00001/status&amp;lt;br/&amp;gt;{&amp;#34;status&amp;#34;:&amp;#34;online&amp;#34;,&amp;#34;signal_strength&amp;#34;:22,...}
E-&amp;gt;&amp;gt;B: 路由消息 (通配符订阅)
B-&amp;gt;&amp;gt;B: SETEX device:heartbeat:DEV-20260718-00001 90 &amp;#34;{...}&amp;#34;
Note over B: Redis TTL=90s&amp;lt;br/&amp;gt;key 存在 → 在线
end
Note over D,P: ═══ 阶段五: 设备断开 ═══
D-&amp;gt;&amp;gt;E: DISCONNECT / TCP 断开
Note over E: LWT (遗嘱消息) 触发
E-&amp;gt;&amp;gt;B: PUBLISH vending/DEV-20260718-00001/status&amp;lt;br/&amp;gt;{&amp;#34;status&amp;#34;:&amp;#34;offline&amp;#34;}
B-&amp;gt;&amp;gt;B: Redis key 开始倒计时 90s
Note over B: 90s 后 key 自动过期&amp;lt;br/&amp;gt;设备状态 → offline
&lt;/code&gt;&lt;/pre&gt;</description></item></channel></rss>