别再把 HTTPS 和 OTA 看成两回事:一篇讲透 HTTPS 协议、安全通信机制与 Mender 升级加密链路的完整文章


📺 B站博主个人介绍

📘 博主书籍-京东购买链接 *:Yocto项目实战教程

📘 加博主微信,进技术交流群jerrydev


别再把 HTTPS 和 OTA 看成两回事:一篇讲透 HTTPS 协议、安全通信机制与 Mender 升级加密链路的完整文章

前言

很多人学习 OTA 时,容易把问题拆成两半:一半是"升级流程",另一半是"HTTPS 安全"。于是实际工作里经常出现一种很危险的状态:会点页面、会上传 .mender 文件、会看设备日志,但一旦遇到证书错误、UnauthorizedPendingAlready installedNo compatible artifact found 这些状态,就开始混乱。原因不是命令不会敲,而是没有把"安全通信"和"升级链路"放在同一张图里理解

对于 Mender 这样的 OTA 系统来说,升级从来不是"把一个文件发过去"这么简单。它本质上是一条完整链路:设备先用 HTTPS/TLS 访问服务端认证接口,拿到 token,再用 token 去上报 inventory、查询 deployment、下载 Artifact、安装到 A/B 分区中的非活动分区,最后再重启、验证、提交或回滚。也就是说,HTTPS 不是升级前面的附属准备,而是升级体系的一部分 。Mender 官方文档把这些能力拆散在不同页面里:有 Docker Compose 演示环境,有 certificates and keys,有 client configuration,有 device authentication,也有 Yocto 构建 Artifact 和 deployment 的说明。把这些页面分开看很容易懂局部,放在一起看才会真正懂整体。(docs.mender.io)

这篇文章的目标,就是把 HTTPS 协议、安全通信机制、证书与私钥、TLS 握手、服务端身份校验、Mender 的认证与升级过程、Artifact 签名验证、A/B 更新链路 放到同一条主线上来讲。读完之后,你不只是知道"某个报错怎么处理",而是应该能建立一套更稳固的判断习惯:
client 实际连的是谁,谁在终止 TLS,证书是谁签的,设备到底信任什么,token 从哪里来,Artifact 是如何被判定为兼容或已安装,传输安全和内容可信到底分别由什么机制保证。


一、先别急着谈 Mender,先把 HTTPS 本身讲清楚

1. HTTP 是什么,HTTPS 又是什么

HTTP 本质上是一个无状态的应用层协议 ,用来在客户端和服务器之间交换请求与响应。HTTP 自己并不保证保密性,也不保证消息在传输过程中不被窃听和篡改。IETF 在 RFC 9110 中把 HTTP 定义为一种无状态的应用层协议,同时也明确了 httphttps 这两种 URI 方案。(RFC Editor)

HTTPS 则不是一套全新的应用协议,而是:
HTTP over TLS

也就是说,HTTP 仍然是上层语义,TLS 负责给它提供安全通信能力。RFC 2818 的摘要就明确指出,HTTP over TLS 描述的是如何用 TLS 来保护互联网上的 HTTP 连接。(RFC Editor)

所以最先要建立的认识是:

HTTPS = HTTP + TLS。

不是"HTTP 的安全版本"这么一句话就结束,而是要知道这两个部分各自负责什么:

  • HTTP 负责"我要请求什么、服务器返回什么"
  • TLS 负责"这条传输链路是不是机密的、是不是完整的、对面的服务器是不是它自称的那个服务器"

2. TLS 到底解决什么问题

TLS 的目标不是"让连接看起来更高级",而是解决公开网络上的三个核心问题:

  1. 保密性:第三方不能直接读懂通信内容
  2. 完整性:第三方不能在中间悄悄篡改消息而不被发现
  3. 身份认证:客户端要有办法确认它连到的就是目标服务器,而不是中间人伪造的站点

TLS 1.3 的 RFC 8446 概述就指出,TLS 允许客户端和服务器在互联网上通信,并尽量防止窃听、篡改和伪造。RFC 9001 在概述 TLS 时也明确说,TLS 能在不可信媒介上给双方建立通信方式,提供对等体认证,并为保密与完整性提供保护。(RFC Editor)

所以当你说"我已经能 ping 通服务器了",这只说明网络层可能通了

但 HTTPS/TLS 关心的是:
你到底是不是连到了那个你想连的服务器,你们之间的内容是不是加密的,这个连接有没有被中间人冒充。

这就是为什么很多时候设备能访问 IP,却仍然会报:

  • certificate verify failed
  • hostname mismatch
  • Unauthorized
  • Failed to fetch new token

因为这些已经不是"网络通不通"的问题,而是安全链路有没有真正建立起来的问题。


二、证书、公钥、私钥到底是什么关系

1. 为什么服务器要有证书

很多人第一次接触 HTTPS,会误以为"证书就是公钥文件"。这不完整。

证书本质上包含至少三类关键信息:

  • 服务器身份信息,例如域名
  • 服务器的公钥
  • 由某个签发者对这些信息做的签名

所以证书不是单独的公钥,而是:

"某个主体的身份 + 它的公钥 + 一个签名证明"

对于客户端来说,证书的意义在于:

"我不仅看到了一个公钥,我还看到了这把公钥宣称它属于谁,并且这个宣称被某个我信任的签发者背书。"


2. 私钥在哪里,它到底做什么

证书是可以给别人看的,但私钥绝对不能给别人看

服务器真正秘密持有的是私钥。它的作用不是"解开证书",而是在 TLS 握手里证明:

我确实拥有证书里那把公钥所对应的私钥。

换句话说:

  • 证书里放的是公开信息和公钥
  • 服务器本地保管私钥
  • 客户端在握手时不是"拿到私钥",而是验证服务器是否真能用对应私钥完成签名或密钥交换相关步骤

这就是为什么把 .crt 拷到设备端是合理的,而把 .key 拷到设备端是严重错误的。

设备端需要的是信任某个证书或某个 CA,不是持有服务端私钥。


3. 自签名证书与 CA 签发证书的差别

如果服务器证书是公开 CA 签发的,那么客户端通常只要本机的系统根证书集合足够新,就能完成信任校验。Mender 官方文档明确提到:如果服务端证书由证书颁发机构签发,mender-auth 通常不需要特殊配置,客户端会借助系统根证书来验证信任,根证书通常由 ca-certificates 包提供。(docs.mender.io)

但如果证书是自签名证书 ,情况就不同了。

自签名证书没有被公开 CA 背书,因此客户端默认不会信任它。Mender 官方也明确说明:如果证书是自签名的,客户端必须在本地存储服务端证书,才能验证服务端的真实性。(docs.mender.io)

这条规则非常重要,因为它直接对应你在 Mender Docker 演示环境中看到的现象:

  • 浏览器打开 https://localhost/ui 会告警"不安全"
  • 设备端如果没有导入 demo.crt / mender.crt,会报 certificate verify failed

因此要牢牢记住:

网络打通 ≠ TLS 成功;TLS 成功 ≈ 域名、证书、信任链三者都匹配。


三、TLS 握手到底在发生什么

1. 为什么要握手

在 HTTPS 里,客户端和服务器刚见面时,并没有现成的会话密钥。

所以他们必须先经历一次协商过程,决定:

  • 用哪种 TLS 版本
  • 用哪组加密算法
  • 用什么方式建立共享密钥
  • 服务器如何向客户端证明身份
  • 是否需要客户端也出示证书

TLS 1.3 的规范明确把握手描述成这样一个过程:双方协商协议版本、算法,并认证身份、建立共享密钥材料。(RFC Editor)

所以"握手"这个词不是形容,而是实实在在的协议阶段。


2. 服务器是如何"证明自己"的

这是很多人对 TLS 最模糊的地方。

客户端并不是因为"看到了证书"就直接信任服务器,而是要完成两层判断:

第一层,证书本身是不是可信。

也就是:

  • 证书链能不能追溯到我信任的根
  • 或者这张自签名证书本身是不是在我的信任集合里

第二层,服务器是不是真的拥有 这张证书对应的私钥。

TLS 握手里,服务器会借助私钥完成相应的签名或协商步骤,客户端则用证书中的公钥验证这个过程。RFC 8446 相关内容和后续勘误都强调,握手协议会协商参数、认证对端并建立共享密钥。(RFC Editor)

这就是为什么:

  • 仅仅复制一张别人的证书没用
  • 中间人即使能转发流量,也无法伪造真正的私钥行为
  • 客户端最终能确认"我连到的不是一个冒牌者"

3. 为什么域名必须和证书对得上

很多人把 "证书受信任" 和 "证书名字匹配" 混成一回事。

其实 TLS 校验至少要过两关:

  1. 证书链或证书本身是否受信任
  2. 证书中的名称是否匹配当前访问的主机名

也就是说,如果你的 ServerURL 写的是:

text 复制代码
https://docker.mender.io

那么客户端期望看到的证书,就必须能覆盖 docker.mender.io 这个名字。

如果你把 ServerURL 改成裸 IP,而证书里只有域名,没有这个 IP,那就算证书本身被信任,也仍然会因为名称不匹配而失败。

所以要形成一种固定判断习惯:

先看 client 连的是谁,再看证书里写的是谁,再看 client 到底信任谁。

只要这三者不是一一对应,TLS 就不是"差一点就成功",而是根本不成立。


四、Mender 里的 HTTPS 安全链路到底怎么接上 OTA

1. Mender client 不是直接"升级",而是先认证

在 Mender 里,设备不是一启动就直接下载更新。

它必须先完成身份认证。这个过程主要由 mender-auth 负责。Mender 官方文档把它直接写出来了:mender-auth 属于 Mender Client 组件,负责和服务端的认证链路打交道。(docs.mender.io)

从设备视角看,顺序是这样的:

  1. 读取本地配置,例如 /etc/mender/mender.conf
  2. 读取 device_type 等信息
  3. 使用本地生成的设备密钥发起认证
  4. 通过 HTTPS 调用认证接口
  5. 如果认证通过,拿到新的 authorization data / token
  6. 后续才能开始 inventory 和 deployment 的查询

所以设备日志里只要出现:

  • Failed to fetch new token
  • certificate verify failed
  • Unauthorized

就说明问题还停留在认证链路,不是升级链路。


2. token 在 Mender 里到底意味着什么

token 最容易理解的方式是:

服务端在认证通过后发给设备的一张临时访问凭证。

它不是设备永久身份,也不是版本号,更不是 Artifact 的一部分。

它只是说:

"你已经通过了认证,接下来在一段时间里,你可以拿着这张凭证访问后续 API。"

所以在 Mender 里,mender-update 想做这些事情,都要依赖 token:

  • 上报 inventory
  • 查询 deployment
  • 下载更新
  • 上报安装状态

这也是为什么一旦拿不到 token,后面就会连锁出现:

  • Request to push inventory data failed
  • Request to check new deployments failed
  • Cannot submit API request

根本原因不是 inventory 本身坏了,而是认证通行证没有拿到


3. Pending、Accepted、Unauthorized 分别处在什么阶段

这是 Mender OTA 里最容易被误解的一组状态。

Pending

设备第一次把认证请求送到服务端后,通常会先出现在 Pending。

这表示:

  • Server 已经看到这台设备
  • 但还没有授权它成为正式受管设备
Accepted

管理员在 Web 界面点了 Accept 后,这台设备才会进入 accepted 状态。

Unauthorized

如果设备还没有被授权,但已经在继续发起认证,就会在日志里看到:

  • dev auth: unauthorized

所以 Unauthorized 并不是"你找错了服务器",而更像是:

我已经找到你了,但你还没正式放行我。

当你点击 Accept 之后,设备重新认证,日志里就会变成:

  • Successfully received new authorization data

这说明:
安全链路已经从"TLS 成功但未授权"进入了"认证通过并拿到通行证"。


五、Mender client 的本地配置为什么这么关键

1. mender.conf 在 Mender 里的位置

Mender 官方文档明确说明:客户端的大部分配置在 /etc/mender/mender.conf 中,它是一个 JSON 文件;另外还可以有位于 /var/lib/mender/mender.conf 的 fallback 配置,因为 /var/lib/mender 处于持久化存储上,不会被更新覆盖。(docs.mender.io)

这意味着,Mender client 真正工作的很多关键点,最终都落到本地配置文件上。


2. ServerURLServerCertificate 的关系

官方文档给出的典型示例就是:

json 复制代码
{
  "RootfsPartA": "/dev/hda2",
  "RootfsPartB": "/dev/hda3",
  "ServerURL": "https://mymenderserver.net",
  "ServerCertificate": "/etc/site-conf/server.crt"
}

这个例子非常重要,因为它一口气说明了几件事:
ServerURL 不是随便写个 IP 就完事,而是告诉 client 它要连的 HTTPS 服务器地址
ServerCertificate 则告诉 client:如果这不是一个系统默认就信任的 CA 证书链,那我应该额外信任哪一张服务端证书。(docs.mender.io)

所以以后看到 mender.conf,要第一时间联想到:

  • client 实际连的是谁
  • 证书是如何被信任的
  • 这个地址和证书里的名字是否匹配

而不是只把它看成"配置文件"。


3. 为什么设备端不能直接写裸 IP

这就是你这轮学习里非常值得强化的一个判断点。

从网络上看,裸 IP 当然能访问。

但从 HTTPS/TLS 角度看,如果证书是给 docker.mender.io 签的,而你把 ServerURL 写成:

text 复制代码
https://192.168.50.131

那么证书名称和目标主机名就不一致。

也就是说,client 看到的不是"我去连一个等价对象",而是"我去连一个名字完全不同的服务器"。

因此要形成一个固定习惯:

不是"IP 能通就行",而是 ServerURL、证书中的域名、client 侧的信任对象必须一一对应。


六、Mender 的传输安全和内容可信其实是两套机制

这是一个特别容易混淆,但又特别重要的点。

1. 传输安全:靠 HTTPS/TLS

传输安全解决的是:

  • 设备到服务端的通信会不会被窃听
  • 会不会被中间人篡改
  • 设备到底连到的是不是真正的 Server

这个层面主要看:

  • ServerURL
  • ServerCertificate
  • 系统 CA 信任
  • 自签名证书是否已导入
  • TLS 握手是否成功

2. 内容可信:靠 Artifact 签名验证

内容可信解决的是另一件事:

  • 这个 .mender 更新包是不是被授权的人制作的
  • 在分发过程中有没有被掉包
  • 设备安装前能不能验证它的签名

Mender 官方在 mender.conf 配置选项里明确区分了:

  • ArtifactVerifyKey / ArtifactVerifyKeys
  • 设备端使用公钥验证 Artifact 签名
  • 如果要求签名但签名无效或缺失,客户端应拒绝安装。(docs.mender.io)

而在 Yocto 的生产构建文档里,又进一步强调:

  • 用于签名 Artifact 的私钥必须受保护,并且应放在构建系统之外
  • 设备端只需要保存公钥来验证签名。(docs.mender.io)

这说明:

HTTPS 证书和 Artifact 签名密钥不是一回事。

  • HTTPS 证书:保障"通信这条路是安全的"
  • Artifact 签名:保障"送到设备上的包是可信的"

如果把这两者混在一起,就会在设计上留下很大的漏洞。


七、Yocto 构建出来的 .mender 到底是什么

1. .mender 不是普通文件副本

在 Mender 官方的 Yocto 文档里,构建输出被明确区分为:

  • 首次 provisioning 用的磁盘镜像
  • 后续可部署到已刷机设备上的 .mender Artifact。(docs.mender.io)

而 Artifact 文档又说明,Artifact 本质上是一个包含 payload、元数据和版本信息的归档文件。它不仅仅是个 rootfs 文件副本,还包含 Mender server 和 client 用来理解更新的结构信息。(docs.mender.io)

2. 为什么 .mender 往往很大

因为在标准 rootfs-image 模式下,.mender 通常承载的是完整的 rootfs payload。

所以只要你的 rootfs 本身很大:

  • .ext4
  • .mender 也会跟着大

这不是 Mender 固定要"大文件",而是因为你当前采用的是整 rootfs 系统镜像 OTA

3. .mender 和 A/B 分区不是"双打包关系"

很多初学者会直觉认为:

既然设备是 A/B 双分区,那 .mender 里是不是应该同时有 A 和 B 两套根文件系统?

其实不是。

更准确的理解是:

  • A/B 是设备侧预先存在的分区结构
  • .mender 通常只携带一份新的 rootfs payload
  • 安装时把这份 payload 写到当前非活动分区
  • 重启后切换过去
  • 成功则 commit,失败则 rollback

所以最应该记住的是:

A/B 是设备侧的"位置结构",Artifact 是要被写进去的"新内容"。


八、Mender 为什么会说 No compatible artifact found

这不是网络问题,也不是证书问题,而是兼容性问题

服务端会把设备上报的 device_type 与 Artifact 声明的兼容设备类型进行比较。

如果设备端是:

text 复制代码
jcl-tablet-p3701-0004

而 Artifact 声明的是:

text 复制代码
jcl-tablet-p3701-0005

那服务端就会判断:

这个包不是给这台设备的。

于是 Deployment 页面就会显示:

  • No compatible artifact found

这一步已经说明:

  • 网络打通了
  • 设备认证成功了
  • Inventory 也正常了

只是包不匹配

所以以后看到这个状态,就不要再去改证书或查 token 了,而是应直接检查:

  • 设备端的 device_type
  • 构建时的 MENDER_DEVICE_TYPE
  • .mender 内部声明的 compatible devices

九、Mender 为什么会说 Already installed

这同样不是网络或 TLS 问题,而是版本判断问题

1. 设备会告诉 Server 自己当前安装的 Artifact 名字

设备端执行:

bash 复制代码
mender-update show-artifact

会返回当前 Mender 认定的 Artifact 名字,例如:

text 复制代码
my-jetson-image-ota_1.0.2

show-provides 还能进一步展示当前版本相关的 provides 信息,例如 rootfs-image.version。这说明对于 Mender 来说,当前已安装的软件版本已经有一个明确名字。

2. 如果你下发的 Release 名字也一样

那服务端和设备端的结论都会是:

这版本我已经装过了。

于是你仍然可以创建 Deployment,但设备会跳过安装,结果表现为:

  • Already installed
  • Skipped = 1
  • Total download size = 0 Bytes

这也是为什么"同版本能发 Deployment,但不会真正重复升级"。

3. MENDER_ARTIFACT_NAME 和当前已安装版本的关系

构建时定义的:

conf 复制代码
MENDER_ARTIFACT_NAME = "my-jetson-image-ota_1.0.2"

最终会反映到设备当前上报的 artifact_name 上。

所以如果你想真正触发一次新升级,就必须让新的构建产物拥有不同的 Artifact 名字 ,例如 1.0.3,否则它会一直被判定为"已经安装过"。


十、为什么 /etc/os-releasemender-update show-artifact 会不一致

这是一个特别容易迷惑人的现象,但也正好能帮助你建立更稳的判断习惯。

你可能会看到:

  • /etc/os-release 显示类似 my-jetson-image-ota_1.0.3
  • mender-update show-artifact 仍然显示 my-jetson-image-ota_1.0.2

这说明:

系统文本版本和 Mender 逻辑里当前认定的 Artifact 名字,不一定同步。

/etc/os-release

它本质上只是系统里的一个普通文本版本信息文件,更多是给人看和给系统组件读取展示的。

mender-update show-artifact

它则是 Mender client 当前认定、并且会用于 OTA 判断的实际 Artifact 名字。

因此在 OTA 分析时,你更应该信的是:

bash 复制代码
mender-update show-artifact

而不是单纯依赖 /etc/os-release

这也说明了一个很重要的认知:

升级系统的"显示版本"与升级系统的"内部状态机版本"是两层不同的信息。


十一、为什么设备上没有 artifact_info 文件也不代表有问题

很多人看到官方或别人的系统里有 artifact_info,就会以为这一定是判断当前版本的唯一来源。

但实际中,设备没有这个文件也很正常。

原因在于:

  • artifact_name 不一定非得以明文文件存在
  • Mender client 也可以从自己的状态数据里知道当前版本
  • 只要 Server 页面里已经能看到设备上报的 artifact_name
  • 就说明客户端已经把这个值上报成功了

所以以后不要再把"有没有 artifact_info 文件"当成唯一判断依据。

更可靠的判断方式是:

bash 复制代码
mender-update show-artifact
mender-update show-provides

以及服务端看到的 Device Inventory。


十二、从安全视角再看一遍整个 Mender 升级链路

现在把所有内容收束成一条更完整、更偏安全视角的主线:

第一步:构建阶段

在 Yocto 构建机里定义:

  • MENDER_DEVICE_TYPE
  • MENDER_ARTIFACT_NAME

并构建出 .mender Artifact。

如果你启用了 Artifact 签名,还会在构建阶段用私钥对 Artifact 签名,设备端之后使用公钥验证。(docs.mender.io)

第二步:Server 部署阶段

在 Docker Compose 演示环境中:

  • docker.mender.io / s3.docker.mender.io 配置 hosts
  • 服务端通过自签名证书提供 HTTPS
  • Web 界面可通过 https://localhost/ui 访问。(docs.mender.io)

第三步:设备配置阶段

设备通过 mender-setup 或预置 mender.conf 知道:

  • 我要连谁(ServerURL
  • 我是谁(device_type
  • 我要信任哪张服务端证书(ServerCertificate)(docs.mender.io)

第四步:TLS 建立阶段

设备访问 https://docker.mender.io,开始 TLS 握手:

  • 检查证书是否被信任
  • 检查域名是否匹配
  • 验证服务器是否真拥有私钥

如果失败,就停在:

  • certificate verify failed

第五步:认证阶段

TLS 成功后,mender-auth 发起认证:

  • 设备身份与密钥参与认证
  • 服务端根据授权状态返回结果

如果没 Accept,就会出现:

  • Unauthorized

如果 Accept 成功,则设备拿到 token。

第六步:Inventory 阶段

mender-update 开始上报:

  • device_type
  • artifact_name
  • 其他 inventory 项目

第七步:Deployment 匹配阶段

服务端比较:

  • 当前设备 device_type
  • 当前设备 artifact_name
  • 新 Artifact 的 compatible devices
  • 新 Artifact 的 artifact name

于是产生三种结果:

  • 类型不匹配 → No compatible artifact found
  • 版本相同 → Already installed
  • 类型匹配且版本更新 → 开始真正 OTA

第八步:安装与启动阶段

设备将 Artifact payload 写到非活动分区,重启切换并验证:

  • 成功则 commit
  • 失败则 rollback

这样一整套安全链和升级链就闭环了。


结语

如果把这篇文章最后再压缩成一句最关键的话,那就是:

在 Mender 里,HTTPS/TLS 不是升级前面的附属准备,而是升级链路的一部分;只有把"谁在终止 TLS、client 实际连的是谁、证书和 ServerURL 是否匹配、设备到底信任什么、token 从哪里来、Artifact 如何被判定兼容或已安装"这些点全部放在一张图里,你才真正算理解了 Mender 的加密机制和 OTA 链路。

以后再看到这些状态时,你就不该只记住"怎么改命令",而应该能立刻判断它们分别属于哪一层:

  • certificate verify failed → TLS 信任链问题
  • Unauthorized → 认证授权问题
  • No compatible artifact found → 设备类型匹配问题
  • Already installed → 当前版本与新 Artifact 名字相同
  • No update available → 客户端工作正常,但服务端当前没有更高版本任务

当这些判断习惯建立起来,HTTPS 和 Mender OTA 就不再是两块分离的知识,而会真正合成一套清晰、可推理、可扩展的安全升级体系。


📺 B站博主个人介绍

📘 博主书籍-京东购买链接 *:Yocto项目实战教程

📘 加博主微信,进技术交流群jerrydev


相关推荐
j_xxx404_1 小时前
C++算法:一维/二维前缀和算法模板题
开发语言·数据结构·c++·算法
高洁012 小时前
学习基于数字孪生的质量预测与控制
人工智能·python·深度学习·数据挖掘·transformer
郝学胜-神的一滴2 小时前
系统设计与面向对象设计:两大设计思想的深度剖析
java·前端·c++·ue5·软件工程
蓝天智能2 小时前
QT实战:Qt6 字符编码避坑指南
开发语言·qt
xier_ran2 小时前
【第一周】关键词解释:倒数排名融合(Reciprocal Rank Fusion, RRF)算法
开发语言·python·算法
HelloWorld__来都来了2 小时前
如何用python爬取上市公司信息
开发语言·python
音视频牛哥2 小时前
Android平台GB28181设备接入模块架构解析、功能详解与典型应用场景分析
android·android gb28181·gb28181安卓端·gb28181对接·gb28181设备·gb28181语音广播·安卓gb28181设备对接
myloveasuka2 小时前
[Java]子类到底能继承父类中的哪些东西?继承中成员变量/方法访问特点---就近原则
java·开发语言
umeelove352 小时前
vscode配置django环境并创建django项目(全图文操作)
java