兰 亭 墨 苑
期货 · 量化 · AI · 终身学习
首页
归档
编辑文章
标题 *
URL 别名 *
内容 *
(支持 Markdown 格式)
# acme.sh申请证书流程解读 `acme.sh` 是一个非常了不起的项目,因为它完全用 **POSIX Shell**(即最基础的 Shell 脚本)实现了复杂的 ACME 协议(RFC 8555),没有依赖 Python 或 Go 等高级语言环境。 要理解它如何实现证书申请,我们需要深入其核心文件 `acme.sh`。整个流程严格遵循 ACME v2 协议。 以下是基于源码逻辑的深度分析,我们将证书申请流程拆解为 **入口、核心调度、协议交互、验证逻辑、后续处理** 五个部分。 --- ### 1. 入口与参数解析:`issue` 当你执行 `acme.sh --issue -d example.com -w /var/www/html` 时,脚本的执行流程如下: 1. **参数解析**:脚本最底部的 `main()` 函数处理命令行参数,识别到 `--issue`,将 `_cmd` 变量设为 `issue`。 2. **分发执行**:脚本调用 `issue()` 函数(这是对外的主入口)。 3. **环境检查**:`issue()` 会做一系列初始化: * 解析所有域名(`-d` 参数)。 * 检查是否已经有账户(ACME 协议需要账户注册),如果没有则自动调用 `register_account`。 * 确定验证模式(DNS 模式、Webroot 模式、Standalone 模式等)。 最终,`issue()` 会调用核心的内部函数 **`_issue`**(注意前面的下划线),这是真正干活的地方。 --- ### 2. 核心循环:`_issue` 函数 `_issue` 函数实现了 ACME 协议定义的标准“订单(Order)”流程。源码中这个函数非常长,其逻辑步骤如下: #### A. 创建订单 (New Order) * **源码对应**:`_newOrder` * **逻辑**: * 它构造一个 JSON Payload,包含你申请的所有域名。 * 调用 `_post` 函数将请求发送给 CA(如 Let's Encrypt)的 `/new-order` 接口。 * **关键点**:所有的网络请求都封装在 `_post` 和 `_get` 中,底层使用 `curl` 或 `wget`。 * **返回**:CA 返回一个 JSON,包含 `authorizations`(验证地址列表)和 `finalize`(最终提交 CSR 的地址)。 #### B. 获取验证详情 (Fetch Authorization) * **源码对应**:`_getAuthz` * **逻辑**: * 遍历订单中的每个域名,去获取它们的验证要求。 * CA 会告诉脚本:“对于 `example.com`,你需要证明你拥有它。你可以选择 `http-01` 或 `dns-01` 方式,这是你需要填写的 `token` 和 `keyAuthorization`。” #### C. 触发验证动作 (Run Validation) 这是最复杂的部分,脚本根据你选的模式(HTTP 或 DNS)执行具体操作。 * **源码对应**:`_run_validation` * **逻辑**: 1. **HTTP-01 模式**: * 调用 `_start_http_verify`。 * 它会在你的网站根目录创建 `.well-known/acme-challenge/` 目录。 * 写入一个包含 Token 的文件。 2. **DNS-01 模式**: * 调用 `_start_dns_verify`。 * 这里用到了著名的 **DNS API 机制**。源码中会 `source` 导入 `dnsapi/` 目录下的对应脚本(例如 `dns_cf.sh`)。 * 执行 `dns_myapi_add` 函数,调用云厂商 API 添加 TXT 记录。 #### D. 触发 CA 验证 (Trigger Challenge) * **源码对应**:`_respondToChallenge` * **逻辑**: * 当你把文件放好,或者 TXT 记录加好后,`acme.sh` 会向 CA 发送一个 POST 请求,说:“我准备好了,你来查吧”。 #### E. 轮询状态 (Polling Status) * **源码对应**:`_check_authz_status` * **逻辑**: * CA 的验证是异步的。脚本进入一个 `while` 循环。 * 每隔几秒查询一次验证状态 (`pending` -> `processing` -> `valid` 或 `invalid`)。 * 如果变更为 `valid`,则进入下一步;如果 `invalid`,则报错退出。 #### F. 生成与提交 CSR (Finalize Order) * **源码对应**:`_createCSR`, `_finalizeOrder` * **逻辑**: * **生成 CSR**:调用 `_createCSR`。这里 `acme.sh` 直接调用系统的 `openssl` 命令生成私钥(如果需要)和 CSR 文件。 * **提交**:将 CSR 内容通过 `_post` 发送到之前获取的 `finalize` 接口。 #### G. 下载证书 (Download Cert) * **源码对应**:`_downloadCert` * **逻辑**: * 一旦 Order 状态变为 `valid`,CA 会提供证书下载链接。 * 脚本下载 PEM 格式的证书,并处理证书链(Fullchain)。 --- ### 3. 技术难点与实现细节 你可能会好奇,Shell 怎么处理 JSON 和加密签名?这正是 `acme.sh` 的黑科技之处。 #### 1. JWS 签名 (JSON Web Signature) ACME 协议要求所有请求必须经过 JWS 签名。 * **源码对应**:`_jws()` * **实现**: * 它利用 `openssl dgst -sha256 -sign` 对 JSON 头和 Payload 进行签名。 * 然后利用自定义的 `_base64` 函数将签名结果进行 URL 安全的 Base64 编码。 * 最后拼接成 ACME 需要的 JWS 格式结构体。 #### 2. JSON 解析 Shell 没有原生的 JSON 解析器。 * **实现**: * `acme.sh` 大量使用了 `grep`, `sed`, `awk` 来提取 JSON 字段。 * 例如函数 `_egrep_o`,它是对 `grep -o` 的封装,配合正则表达式提取 Token 或 URL。 * 这种方式虽然看起来“笨拙”,但保证了极高的兼容性,不需要安装 `jq`。 #### 3. 插件化架构 (DNS API) * **实现**: * 主程序不包含具体的 DNS 厂商逻辑。 * `dnsapi/` 目录下的每个 `.sh` 文件都遵循统一的命名规范(`dns_provider_add`, `dns_provider_rm`)。 * 在运行时,主程序使用 `source "$_api_file"` 动态加载这些函数,实现了类似于“接口”和“实现”的分离。 --- ### 4. 源码阅读简易图解 如果你打开源码,可以按照这个调用栈去追踪: ```text issue() <-- 用户入口 | +-> _checkAccount() <-- 确保有账户 | +-> _issue() <-- 核心逻辑开始 | +-> _newOrder() <-- 1. 向 CA 下单 | +-> _getAuthz() <-- 2. 获取验证要求 | +-> _run_validation() <-- 3. 部署验证文件或 DNS 记录 (调用插件) | +-> _respondToChallenge() <-- 4. 告诉 CA "我好了" | +-> _check_authz_status() <-- 5. 循环等待 CA 验证结果 | +-> _createCSR() <-- 6. 调用 OpenSSL 生成 CSR | +-> _finalizeOrder() <-- 7. 提交 CSR | +-> _downloadCert() <-- 8. 下载证书 ``` ### 总结 `acme.sh` 申请证书的核心在于:**它将 ACME 协议的 HTTP 交互流程,翻译成了标准的 Shell 函数调用。** 它最大的技术亮点在于: 1. **纯 Shell 实现 JWS 签名**:绕过了编程语言库的依赖。 2. **文本处理黑魔法**:用基础 Linux 命令处理复杂的 JSON 交互。 3. **插件系统**:通过 `source` 机制实现了强大的 DNS API 扩展性。 如果你想学习 Shell 编程的极限,阅读 `acme.sh` 的 `_jws` 函数和 `_post` 函数会让你大开眼界。
配图 (可多选)
选择新图片文件或拖拽到此处
标签
更新文章
删除文章