签名软件包与验证公钥
接触过服务器的朋友对 yum
、rpm
、apt
、apt-get
这些指令应该不陌生,它们属于不同 Linux 发行版的软件包管理系统(Package Management System, PMS),其历史可以追溯到上世纪 90 年代。现代操作系统中,我们常通过软件包管理器来安装或更新软件——服务器系统尤为常见,桌面系统如 macOS 的 brew
也是同类工具。软件包管理器通过从远程仓库拉取并下载,与当前系统匹配的预编译的软件包,简化安装和更新过程。
在安全层面,软件包管理器依赖数字签名技术来确保软件包的完整性和来源可信。开发者会使用自己的私钥为发布的软件包签名,而用户系统则使用开发者提供的公钥来验证下载的完整性(确保软件包完整且未被篡改或伪造)。这一过程可以由软件包管理器自动完成,也可以手动执行。例如,在基于 RPM 的系统上可以使用 rpm --checksig package.rpm
来验证签名。比如在一个centOS7服务器检测手动下载的openssl
包:
rpm --checksig openssl-1.0.2k-25.el7_9.x86_64.rpm
openssl-1.0.2k-25.el7_9.x86_64.rpm: rsa sha1 (md5) pgp md5 OK
GPG
GPG 是 GNU Privacy Guard 的缩写,也称 GnuPG,是一个免费软件,用于确保分发文件的真实性。
各系统的密钥管理方式
Debian/Ubuntu 系统
早期的apt-key
(已弃用)
在较早的 Debian 和 Ubuntu系统中,apt-key
工具被用来管理软件包验证公钥。用户可以通过 apt-key add filename
命令将公钥添加到系统中,通过该工具添加密钥默认被存储在 /etc/apt/trusted.gpg
文件中。apt-key list
则用于列出已添加的公钥。该命令默认会显示所有存储在 /etc/apt/trusted.gpg
以及 /etc/apt/trusted.gpg.d/
目录下的密钥。
通过man apt-key
可以看到:
apt-key(8) will last be available in Debian 12 and Ubuntu 24.04.
在Debian 12和 Ubuntu 24.04之后,apt-key
将被完全弃用,我的系统是Ubuntu 24.04 所以还可以使用list
指令,同时检查到trusted.gpg.bak
文件备份,可以测试
apt-key list --keyring /etc/apt/trusted.gpg.bak
该工具将遍历/etc/apt/trusted.gpg.d/
目录下的每个文件,将其中的公钥与备份的公钥二进制文件合并显示。
/etc/apt/trusted.gpg.d/syncthing.gpg
------------------------------------
pub rsa4096 2024-11-24 [SC]
FBA2 E162 F2F4 4657 B38F 0309 E566 5F9B D597 0C47
uid [ unknown] Syncthing Release Management <[email protected]>
sub rsa4096 2024-11-24 [E]
/etc/apt/trusted.gpg.bak
------------------------
pub rsa4096 2024-11-24 [SC]
FBA2 E162 F2F4 4657 B38F 0309 E566 5F9B D597 0C47
uid [ unknown] Syncthing Release Management <[email protected]>
sub rsa4096 2024-11-24 [E]
pub rsa4096 2015-03-16 [SC]
DF00 FAF1 C577 104B 50BF 1D00 93D6 889F 9F0E 78D5
uid [ unknown] Igor Pecovnik <[email protected]>
uid [ unknown] Igor Pecovnik (Ljubljana, Slovenia) <[email protected]>
sub rsa4096 2015-03-16 [E]
治标不治本:受信任的公钥列表(不推荐)
一种改良方法是将手动编辑/etc/apt/trusted.gpg.d/
目录下的文件,增加.asc
或.gpg
格式的公钥文件,文件名提供公钥用途的解释。参考apt-key
工具中的建议:
add filename (deprecated)
...
Note: Instead of using this command a keyring should be placed directly in the /etc/apt/trusted.gpg.d/ directory with a descriptive name and either "gpg" or "asc" as file extension
但这种方法实际上只是对原来单一文件进行了拆分,并不安全。在该目录下的公钥文件都会被所有仓库信赖,参考 stark overflow上的讨论[1]。
推荐的细粒度公钥管理方式
上述公钥列表方案也即将被废弃,apt (2.9.24) 更新报告表示不再将该公钥列表认定为安全的配置方式[2]:
apt (2.9.24) unstable; urgency=medium
/etc/apt/trusted.gpg is no longer trusted. Setting the Dir::Etc::trusted option manually continues to work for some more time.
Debian 和 Ubuntu 推荐将每个仓库的公钥存储在仅root可写的目录下,比如 /usr/share/keyrings/
,并在仓库源(/etc/apt/sources.list
和/etc/apt/sources.list.d/
目录)通过 signed-by
参数指定对应的公钥文件。相较于先前较为粗暴的单一白名单文件,这种细粒度的管理方式,让每个仓库公钥可以独立管理,避免公钥信任扩展,同时提高配置的灵活性。具体而言分为以下步骤
- 新建目录
如果没有独立的PGP钥匙串目录,新建并按推荐方式赋予权限[3]:
sudo mkdir -m 0755 -p /etc/apt/keyrings/
一般而言这个过程只对于 Debian 12 和 Ubuntu 22.04 之前的系统是必须的,使用者可以自行检查是否已经有该默认目录用来存放公钥。
- 下载公钥
从受信任来源下载 APT 仓库的公钥文件:
wget -O- <https://example.com/key/repo-key.gpg> | gpg --dearmor | sudo tee /usr/share/keyrings/<myrepository>-archive-keyring.gpg
解释其中的指令
wget
从受信任来源https://example.com/key/repo-key.gpg
下载公钥并输出至控制台stdout
(参数-O-
)。这里的 URL 应替换为你想要下载的公钥地址,比如dockergpg --dearmor
使用 gpg 工具 unpack 符合 OpenPGP ASCII armor的公钥sudo tee /usr/share/keyrings/<myrepository>-archive-keyring.gpg
以管理员身份(/usr/share/keyrings
目录只有root写权限),读取unpack后的公钥并将其写入该目录下的文件,<myrepository>
应替换为描述公钥用途的名称,按照要求一般要包含被签名仓库名称。
也可以通过 curl
下载,并通过 gpg
的 --output <file>
或 -o <file>
合并后两条命令指定输出目录:
curl -fsSL <https://example.com/key/repo-key.gpg> | sudo gpg --dearmor --output /usr/share/keyrings/<myrepository>-archive-keyring.gpg
- 编辑源
上述只是添加公钥,对于软件包管理工具我们需要进一步编辑软件源,指明其使用的是哪一个公钥并验证,以apt
工具为例。如果手动添加源,应该在/etc/apt/sources.list.d/
新建.sources
文件,文件格式可使用单行格式(one-line format)或者使用更新的 DEB822 格式(DEB822 format)。以下是两种格式的示例:
- 单行格式(one-line format):
deb [signed-by=/usr/share/keyrings/<myrepository>-archive-keyring.gpg] https://example.com/debian stable main
- DEB822 格式(DEB822 format):
Types: deb
URIs: https://example.com/debian
Suites: stable
Components: main
Signed-By: /usr/share/keyrings/<myrepository>-archive-keyring.gpg
apt
(2.3.10)及更新版本还支持在sources.list
中内嵌公钥[1]。
macOS 系统
在 macOS 上,Homebrew 使用 GPG 签名来验证软件包的完整性和来源。用户可以通过 Homebrew 安装 GPG 工具,并使用 GPG 密钥对软件包进行签名和验证。