资讯动态

portless 每域名证书原理揭秘:为什么 .localhost 子域名不能用通配证书?

发布时间:2026/9/16 10:33:44 来源:尧图企业网站定制
portless 每域名证书原理揭秘为什么 .localhost 子域名不能用通配证书【免费下载链接】portlessReplace port numbers with stable, named local URLs. For humans and agents.项目地址: https://gitcode.com/GitHub_Trending/por/portlessportless 是一个把localhost:3000这类端口地址替换成稳定命名地址如https://myapp.localhost的本地开发工具它的默认 HTTPS 体验背后藏着一个关键设计每域名证书per-hostname certificate机制——而不是用一张*.localhost通配证书搞定一切。本文带你快速看懂 portless 每域名证书的签发原理以及为什么.localhost子域名天生与通配证书无缘。一、先搞懂portless 的证书体系长什么样portless 首次运行时会自动完成三件事生成一个本地 CA证书颁发机构有效期 10 年CN 为portless Local CA用该 CA 签发一张默认服务器证书有效期 1 年把 CA 加入系统信任库macOS 钥匙串 / Linux 各发行版 CA 目录 / Windows 证书存储默认服务器证书的 SANSubject Alternative Names证书里声明我能代表哪些域名的字段只有三项DNS:localhost, DNS:*.localhost, DNS:*.local注意*.localhost看起来能覆盖myapp.localhost但浏览器不认。这正是下面要讲的坑。所有签发逻辑集中在 certs.ts其中默认证书的 SAN 定义在 certs.ts 的generateServerCert函数里。二、核心问题为什么 .localhost 子域名不能用通配证书RFC 2606 把 .localhost 定成了顶级域RFC 2606 §2 将.localhost保留为一个保留顶级域TLD也就是说在名字结构上localhost ← 它自己就是顶级 myapp.localhost ← 相当于 myapp 域 chat.myapp.localhost ← 相当于 chat 子域这带来两个直接后果通配符位置不合法通配证书*.localhost的通配符正好落在顶级域边界上。按照公后缀Public Suffix规则与 TLS 命名规范的精神顶级域层级不允许出现通配证书因为那等于一张证书代表整个顶级域下的所有站点安全模型上不成立。浏览器直接拒绝Chrome、Firefox 等实现遵循这一约束——即使你拿到一张技术上签得没毛病的*.localhost证书访问myapp.localhost时浏览器仍会判定 SAN 不匹配显示ERR_CERT_AUTHORITY_INVALID或名称不匹配警告。换句话说这不是 portless 的 bug而是规范层面的硬限制。这也是为什么很多本地 HTTPS 方案mkcert、self-signed 脚本都采用一个域名签一张证书的做法。对比正常二级域名为什么能用通配证书以example.com为例域名*.example.com是否可用原因shop.example.com✅ 可用通配符在二级域符合规范a.b.example.com❌ 不可用通配符只匹配一层跨不过多级子域myapp.localhost❌ 不可用*.localhost落在保留 TLD 边界规范禁止chat.myapp.localhost❌ 不可用同上且通配只匹配一层可以看到.localhost的处境比多级子域还惨连最顶层的通配都不被允许每个具体名字都必须有一张精确 SAN 的证书。三、portless 的解法SNI 按需签发每域名证书既然通配证书走不通portless 的思路就是浏览器要访问哪个域名就现场给它签哪个域名的证书。整个机制由 SNIServer Name IndicationTLS 握手中客户端提前告知服务器我要访问的域名驱动实现在 createSNICallback裸localhost→ 直接返回默认服务器证书零开销内存缓存命中→ 直接返回已建立的 TLS 上下文磁盘缓存命中→ 读取host-certs/目录里已签发过的证书文件如myapp_localhost.pem验证未过期、签名强度足够后加载全部未命中→ 异步调用 OpenSSL 为该主机名现场生成密钥 证书SAN 包含精确主机名和同层兄弟通配两项例如访问chat.myapp.localhost会签出DNS:chat.myapp.localhost DNS:*.myapp.localhost签发逻辑在 generateHostCertAsync。注意这里 SAN 里的*.myapp.localhost是安全的——它不在 TLD 边界上通配符位于普通域层完全符合规范能顺便覆盖同项目下的兄弟应用。几个工程细节让这套现场办公的体验丝滑无比异步签发openssl 调用走异步执行不阻塞 Node 事件循环其他请求照常处理并发去重同一域名同时发起多个握手时用 pending Promise 表合并成一次签发避免重复劳动文件安全主机名里的点会转成下划线做文件名myapp.localhost→myapp_localhost.pem超过 64 字符的超长主机名会截断 CN因为现代 TLS 校验只看 SAN不看 CN自动续期证书到期前 7 天触发重新生成无需人工干预四、证书文件都在哪里portless 把每个用户的状态放在~/.portless目录相关文件的布局是文件作用ca.pem/ca-key.pem本地 CA 证书与私钥有效期 10 年server.pem/server-key.pem默认服务器证书覆盖localhosthost-certs/*.pem按需签发的每域名证书按主机名命名ca.trustedCA 已加入系统信任库的指纹标记用于快速跳过 OS 检查其中信任状态检查与 WSL 双信任库处理同时更新 Linux 和 Windows 信任存储见 certs.ts 的isCATrusted。如果你曾跳过首次的信任提示可以随时用portless trust补上更多说明见官方文档的 HTTPS 页面。五、行为验证测试用例怎么把关项目用测试锁住了这套行为的关键断言位于 certs.test.ts单级子域myapp.localhost必须落到每域名证书且 SAN 含精确项DNS:myapp.localhostL273-L292多级子域chat.myapp.localhost同样生成精确 SAN 证书L294-L312自定义 TLD如local.example.dev下同层兄弟通配DNS:*.local.example.dev会被正确加入 SAN超长主机名64 字符也能正常签发因为校验依据 SAN 而非 CN重复请求命中内存缓存返回同一个 TLS 上下文对象六、总结一张图讲清楚原理浏览器访问 https://chat.myapp.localhost │ SNI 告知域名 ▼ portless 代理检查 SNI 回调 ├─ localhost → 默认证书 ├─ 内存/磁盘有缓存 → 直接复用 └─ 都没有 → OpenSSL 现场签发 本地 CA ──► 精确 SAN 证书1 年有效 ▼ 浏览器校验SAN 精确匹配 本地 CA 已在信任库 → 绿锁 ✅一句话总结.localhost是 RFC 2606 保留的顶级域*.localhost通配证书落在公后缀边界上、被 TLS 规范和浏览器联手封杀portless 用本地 CA SNI 按需签发每域名证书 多级缓存优雅绕开了这个限制让每个本地应用都拥有自己的可信 HTTPS 命名地址。对于新手来说你完全不需要理解 OpenSSL 细节——portless run myapp next dev之后信任一次 CA剩下的证书生命周期都由 portless 自动打理。【免费下载链接】portlessReplace port numbers with stable, named local URLs. For humans and agents.项目地址: https://gitcode.com/GitHub_Trending/por/portless创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价