在握手中把主机名映射到证书的提示。

SNI 让一个 IP 承载多个 HTTPS 站点

Server Name Indication(简称 SNI)在 TLS 握手中把目标主机名发给服务器,于是同一个 IP 地址能同时承载多张证书、多个站点。在它之前算法很残酷:一张证书占一个地址,一个站点占一个地址,地址像水一样花出去。基于名字的虚拟主机对明文超文本管用,对加密流量却不行——服务器必须在加密请求暴露目标站点之前先选好证书。SNI 把悖论解开了:主机名提前一步,进握手本身,跑在加密前面。 现实成果就是今天的共享边缘:内容分发、平台路由、单机多租户,全靠它把几千张证书复用到几个地址上。但机制有三处利齿值得认识——隐私外泄、老客户端、缺省证书陷阱——每一处都在新租户上线或老客户端投诉时准时咬人。 名字最先报出 在交换密钥之前,客户端就在握手里发出想要的主机名,服务器据此选对虚拟主机和证书。因为名字先于加密到达,nginx 才能在一条连接里同时完成路由与选证书。扩展住在第一条握手报文里,不多花一次往返——选证书和本来就要做的握手在同一趟车里完成。 这个顺序也解释了它为什么不能只靠服务端单方面升级。客户端必须发这个扩展;没学过的客户端拿到缺省证书,拿它去校验自己想要的名字。老旧嵌入库、十年前的支付终端、工业控制器,真实机群里至今还有不带它的——认定全员支持之前,先看清自己的客户端构成。 用 server_name 匹配 主机名是路由元数据,而不是秘密:它以明文往返,负载均衡器或中间设备都能读到。nginx 拿它去和 server_name 匹配,决定走哪个 server 块、用哪张证书。每个站点块自带证书与私钥,共享一个监听套接字的各块按规则比对:精确名先行,再通配头尾,最后正则,和超文本主机头的匹配同一套路。 证书选对还不等于请求一定到对的应用。客户端会先验证证书的 SAN 是否覆盖它发送的名字,握手成功后才发送加密的 HTTP Host;反向代理若在第二层使用了另一套名字,证书看似正确,响应仍可能来自错误租户。把每个租户的 SNI 名、证书 SAN、server_name 和上游路由放在同一份清单里,新增或续期时逐项核对。SNI 解决的是证书选择,不是主机名保密,也不是应用授权。 传输层的站点表和应用层的主机预期必须一致,否则重定向和规范化会和证书打架。典型错配是证书对了、应用错了:握手命中甲块,主机头却进了乙块的逻辑,吐出签名正确、内容错误的响应。两边名字照镜子,两层都测,有分歧就当缺陷修,别当灵活用。 把缺省回退控制好 当虚拟主机一个都没命中时,nginx 会抛出第一个配置的证书作为缺省。因此 listen 443 ssl 块的顺序决定未知名字回退到哪个站点,别人直接敲 IP 也会命中这块。缺省块要招待三种客人:扫地址的扫描器、不带扩展的老客户端、域名过期或打错字误指过来的名字。每种都值得一个刻意的回答——中性落地页、直接断开、静默丢弃——而不是谁先配置谁待客。 ...

二月 7, 2028 · 1 分钟 · XB Field Notes
在 TLS 握手里捎带的、已签名并缓存的 OCSP 应答。

OCSP Staple 免去一段往返校验

把核验交给服务器能省掉一整段往返与一次独立请求;就算没有 Staple,客户端也仍可兜底去查。 服务器只取一次 OCSP stapling 是让「验证」这件事不拖慢握手:客户端本要通过一次额外往返,去 CA 的那台应答服务器(OCSP)查询「这张证书有没有被吊销」。开启 stapling 后,这个查询改成由服务端自己做,然后把查询结果(CA 签过名的那条)放进 TLS 握手一起发过去。浏览器就不用再单独发一次查询了,握手因此更快,也少了一层对 OCSP 提供方是否在线的依赖。 客户端免去一次查询 Staple 的机制是把「每次握手由客户端查」改成「每隔一段时间由服务端查」:服务端以缓存一份带 CA 签名的 OCSP 响应,在窗口内对所有客户端重复“贴”出同一条。每次握手几乎零成本。但 OCSP 响应本身会过期,一般以分钟或天为单位计量;太久、或被 CA 拒签、或验证不过的 staple,会让服务端退回旧路径——干脆不贴。打开的开关在 nginx 里很直白:在 nginx 里把 ssl_stapling 打开,并配置 ssl_stapling_verify 与完整证书链(中间证书)即可。 是 Staple,不是绝对保证 要摆正一个重要认识:stapling 是针对 OCSP 的优化,不是安全边界。它并没有把「吊销检查」做得比不发全程更硬,反而只是把检查挪到了服务器上并缓存。staleness 不影响握手成败——一条过期 staple 更可能让检查退回请求 CA,而不是让 TLS 失败。所以,信任应该建立在 CA 签名与整条证书链之上,而不是 stapling 本身。它真正可度量的收益是省下那一次往返,也消除了 OCSP 提供商宕机时客户端被拖慢的那一层影响。能开就开,几乎没有任何理由不开。 ...

十二月 6, 2027 · 3 分钟 · XB Field Notes
观察、决策、行动三个方框,旁边强调 CT 记录签发而不代表授权。

证书透明度是一本台账,不是机密

证书透明度 CT 让公开 TLS 证书的签发变得可观察,但它不判断域名持有者是否授权了某次签发,不代替证书链验证,也不保证世界上所有证书都已经进入日志。一张错误签发的证书也可能带有有效的透明度证据。真正的价值是留下公共线索,供监控方和受影响的持有者调查。 承诺不等于包含证明 SCT 即签名证书时间戳,是日志对已提交证书或预证书作出的签名承诺:在最大合并延迟 MMD 以内把它纳入树中。SCT 不是另一张证书,也不是证明条目已经存在于树中的包含证明。它的时间戳记录日志作出声明的时刻,不一定就是证书实际签发的精确时刻。 默克尔包含证明把某个已经序列化的叶节点,连接到特定大小的树及其根哈希。验证者需要通过相应的已签名日志声明和可信日志公钥认证这个根。如果无条件接受不可信证明一起附带的根,攻击者完全可以自己构造另一棵树;计算结果相同,也没有建立任何有用的外部事实。 一致性证明回答的是另一个问题:后来的树是否在保持既有前缀不变的情况下扩展了早先的树。一次包含证明不能说明全部历史。签名检查点、一致性验证,以及独立观察者之间对不同视图的比较,有助于发现日志向不同人讲述不同历史。哈希让修改可以被这些检查发现,不是物理上禁止运营者改写数据。 Chrome 对公开受信任的 TLS 证书执行自己的 CT 策略,考虑 SCT 的交付方式、日志状态、数量与运营者多样性。这不是每次打开页面都到公共日志里搜索,也不是判断持有者是否申请过证书。RFC 9162 定义了第二版协议并废止 RFC 6962,但规范发布并不能直接证明某个已部署日志实际支持哪一版。 坏掉的样子 SCT 的签名即使有效,如果对应日志不被接受、数量不足或交付方式不符合目标客户端策略,仍然可能无法通过检查。反过来,符合 CT 也修复不了过期、不受信任或名称不匹配的证书。普通 TLS 验证、CT 合规和签发是否获准,是需要分别确认的三个决定。 域名监控也需要结合业务背景解释。新记录可能来自获准接入的 CDN、通配符证书或正常续签;升级告警之前应核对签发者、名称、指纹与内部签发清单。无法解释的签发应进入调查和纠正流程,不能假定“已经写进日志”就代表它被阻止或被确认合法。 公开日志可能暴露证书中的名称,因此不应仅为做实验就提交私有证书或敏感内部命名。监控覆盖不全、数据过旧,也不能证明某次签发不存在。运营上的监控要声明范围和更新时间,不能把一次空搜索结果当成所有日志、所有时间都没有相关证书的保证。 快速验证 保存为 ct_probe.go,使用 Go 1.23 或更新版本运行 go run ct_probe.go。程序离线使用 RFC 6962 的 SHA-256 默克尔构造,输入是三个合成字符串,不是真实证书,也不是已经按 CT 协议序列化的叶节点。它只验证这个固定三叶树中下标为一的中间叶节点,不是通用验证器。 ...

十一月 3, 2027 · 2 分钟 · XB Field Notes
观察、决策、行动三个方框,旁边强调新的共享密钥仍需身份认证。

TLS ECDHE 保证前向保密

前向保密限制的是长期认证密钥日后泄露造成的影响。对于经过正确认证的临时 Diffie-Hellman 交换,攻击者仅取得长期认证私钥,不能据此恢复先前的会话机密。前提是临时机密没有一起被获取;它不会让已经被攻破的端点、导出的密钥日志或保存下来的明文突然变得安全。 两类密钥承担不同职责 ECDHE 中,双方分别生成临时私钥并交换公开份额。每一方把本地私钥与对方公开份额组合,得到相同的共享机密。旁观者虽然能看到公开份额,但在算法的安全假设成立时,缺少重现计算所需的私有值。新鲜的交换材料并不是双方在网络上公开商量出的一段秘密口令。 TLS 也不会把原始 ECDH 输出直接当成应用加密密钥。密钥调度还会派生不同的握手与应用流量机密,绑定握手上下文,并区分方向和用途。临时交换只提供其中一项关键材料,不能用“双方算出相同字节”替代整个传输协议,也不能略过后续派生和认证步骤。 证书按照信任规则把身份绑定到公钥,实际签署握手材料的是对应的私钥,不是证书这个数据对象自己在签名。TLS 1.3 的证书认证路径通过 CertificateVerify 认证握手记录。如果没有身份认证,主动中间人可以替换公开份额,分别和两端建立自己的共享机密;仅仅看到加密正常,并不能排除这种攻击。 RSA 或 Ed25519 认证密钥可以与临时交换并存,签名算法和密钥交换组是不同选择。TLS 1.3 还定义了仅使用 PSK 的恢复模式,后者不具备 DHE 带来的这项前向保密;PSK 配合 DHE 则会加入新的交换材料。可选 early data 的保证更弱,因此不能单凭 TLS 版本或证书算法标签,就推断所有阶段的流量都有相同保护。 坏掉的样子 长期重复使用同一个交换私钥,会削弱原本希望建立的泄露边界。随机数不足、临时私有值暴露、崩溃转储或 TLS 密钥日志,都可能泄露前向保密假设中不能被攻击者取得的材料。日后泄露流量机密,与日后泄露证书签名私钥,不是同一个威胁场景,不能混用结论。 会话进行时攻破端点,可能直接取得明文。长期认证私钥失窃后,能够拦截未来连接的攻击者也可能冒充服务器。前向保密保护的是特定的历史录制流量场景,并不会撤销泄露凭据,也不会修复仍在持续的入侵;这些问题仍需要独立的凭据轮换与事件处置。 机密销毁更是实现层面的问题,不能仅靠一句“用完就丢弃”来保证。把 Go 变量设为 nil,不证明内存、交换空间或诊断材料中的所有副本都已消失。应避免把机密写进日志,并把凭据失窃的处置与历史流量是否仍受保护的判断分开,不能用概念上的前向保密替代真实的数据暴露调查。 ...

十月 27, 2027 · 2 分钟 · XB Field Notes
TLS 保护一段查询路径,所选解析器仍会收到明文 DNS 问题。

DoT 与 DoH 保护通往已认证解析器的连接

DNS over TLS 和 DNS over HTTPS 保护其端点之间加密连接上的 DNS 流量。对于典型客户端,这条连接终止于选定解析器;解析器能够读取问题与回答,它随后发出的查询还可能使用不同传输。加密这一跳,不会自动加密整个递归过程,也不会让解析器失去记录所处理名称的能力。隐私范围应按真实连接边界说明,而不是笼统称为整个 DNS 都已保密。 坏掉的样子 加密套接字如果没有按预期验证证书,也可能连接到错误端点。应同时验证证书信任链和客户端期望的解析器身份;握手成功本身不能证明这两项检查已启用。失败时悄悄改用其他解析器或明文路径的配置,也会改变隐私契约。需要通过真实调用方了解这些行为,不能只看到配置文件中有一个 TLS 或 HTTPS 地址,就认定所有查询都符合预期。 DoH 使用 HTTPS,但四百四十三端口不会让它在所有网络中都无法识别、无法阻止或完全等同于任意网页流量。目的地址、连接时间和其他元信息仍可观察,TLS 终止点还可以检查明文 HTTP。选择解析器和传输策略时,应考虑这些边界,不能假定 HTTPS URL 会消除所有与 DNS 活动有关的信息暴露,也不能由加密推导解析器不会保存日志。 两种协议的消息边界也不同。DoT 通常使用八百五十三端口,在 TLS 内携带具有两字节 TCP 长度前缀的 DNS 消息。DoH 通常使用四百四十三端口上的 HTTPS 端点。标准 DoH 使用 application/dns-message 传递 DNS 线格式:POST 放在正文,GET 使用经 base64url 编码的 dns 参数。它不是任意 JSON 接口只要套上 HTTPS 就符合标准。 ...

十月 7, 2027 · 3 分钟 · XB Field Notes
观察、决策、行动三个方框,旁边强调验证客户端之后仍需授权。

客户端证书证明的是设备,不只是口令

双向 TLS 在验证服务器身份的同时,也认证客户端是否能够使用证书对应的私钥。但它并不天然证明某台物理设备的身份。可复制的软件私钥可以被带到别处使用;硬件绑定和设备证明需要额外机制。因此,标题中的设备身份应理解为注册和签发策略所赋予的身份,而不是 TLS 自动确认的硬件事实。 完成认证仍然需要授权 TLS 1.3 的证书式客户端认证中,服务器请求证书,客户端通过 CertificateVerify 证明自己能够使用对应私钥。只发送一份公开证书文件并不够。双方都必须应用各自的信任规则,检查证书链和适当用途;客户端还要验证服务器名称,不能因为自己提交了凭据就放弃验证对端。 服务端信任哪些客户端 CA,与客户端信任哪些服务器 CA,是两个方向的决定。应只接受预期签发者,并明确使用哪个已验证证书字段表示工作负载身份。URI SAN 可以存放应用标识,但谁有资格拿到这个标识,取决于签发者的注册规则。证书里出现了名字,不代表它自动拥有所有 API 权限。 “要求必须提供证书”也不同于“请求对方有的话就提供”。Go 的 RequireAndVerifyClientCert 要求有效客户端证书,而可选模式可能允许没有证书的连接通过。认证之后仍需根据已验证身份执行授权,因此合法访客可以得到 HTTP 403,而获准读取的客户端得到 HTTP 200,这两个结果不矛盾。 坏掉的样子 没有证书、签发者不受信任、凭据过期或扩展用途不合适,都可能阻止客户端认证。把服务端改为任意证书都接受,或者关闭客户端对服务器的验证,只是隐藏边界,不是在修复凭据问题。应区分握手失败和应用层拒绝,让排查人员能定位到实际负责的那一层。 私钥需要受控存储、签发、轮换和事件响应。续签证书并不必然更换私钥;被盗且仍被接受的私钥可能用于冒充,不能把等待过期当作唯一处置方式。Go 的证书 Verify 不执行吊销检查,因此发布了一条吊销记录,也不等于每个调用方已经应用它,接受策略必须另行核实。 TLS 终止位置还决定了身份在哪里被建立。后端收到调用方自行填写的身份 Header,不代表后端就验证了调用方证书。受信任的身份传递需要自己的认证边界和防伪造规则;凭据或权限变化时,既有连接、恢复会话如何处理,也必须有明确的生命周期策略。 快速验证 保存为 mtls_probe.go,使用 Go 1.23 或更新版本运行 go run mtls_probe.go。程序在内存中生成客户端 CA 和凭据,把 notBefore 回拨一分钟,并把 notAfter 设为生成时刻之后一小时。回环服务端使用 httptest 自带的测试服务器证书,配套测试客户端信任这张证书。两侧信任库都不安装到系统全局,也不打印或写出私钥。 ...

九月 6, 2027 · 3 分钟 · XB Field Notes
观察、决策、行动三个方框,旁边强调会话恢复不等于零往返承诺。

TLS 会话恢复省一次往返

“省一次往返”需要结合协议版本和 early data 模式理解。TLS 1.3 的普通完整握手,以及不携带 early data 的恢复握手,通常都需要一次 TLS 往返。恢复仍然可以减少证书传输和身份认证开销,但不能把标题理解成“只要开启票据,每个 HTTPS 请求就一定提前一个 RTT 开始”。 新连接仍要派生新的流量密钥 TLS 会话恢复使用先前握手派生的 PSK,为一条新连接建立安全上下文。NewSessionTicket 提供的是与恢复状态关联的不透明标识,不是让后续连接直接沿用原来的流量密钥。新的握手仍会根据自身上下文派生密钥。服务器可以用密码学方法保护票据状态,也可以在不透明标识后保存服务端状态;应用不应把票据解析成一种通用登录凭据。 服务器接受 PSK 认证时,不再发送自己的 Certificate 和 CertificateVerify 消息。恢复可以结合一次新的临时 Diffie-Hellman 交换,获得相应的前向保密;协议也允许仅使用 PSK 的模式,后者没有这项保护。因此,“拿到了票据”既不能证明实际选用了哪种密钥交换,也不能独自证明节省了多少网络等待。 可选的零往返模式允许符合条件的应用数据跟随 ClientHello 一起发送,这与普通会话恢复是两件事。early data 不具有前向保密,并可能跨连接被重放;某条连接后来完成握手,也不能消除另一条连接上已经发生或可能发生的重放。HTTP 服务需要按资源的实际副作用决定能否接受重放风险,并正确处理拒绝,包括规范要求的 425 Too Early 响应。 坏掉的样子 客户端可能根本没有启用票据缓存,可能在处理握手后票据之前就关闭连接,也可能每次请求都重建并丢弃缓存。服务器重启、密钥轮换或票据过期后,也可能不再接受原来的恢复状态。一次恢复尝试最终走完整握手,不自动等于 TLS 服务不可用;恢复比例和建连成功率应该分别观察。 ...

八月 8, 2027 · 2 分钟 · XB Field Notes
观察、决策、行动三个方框,旁边强调跟踪各阶段并区分连接复用。

TLS 握手会拉长延迟,但可以缩小

HTTPS 延迟由多个阶段组成,重复访问可以跳过其中一些阶段,却不一定是加密运算本身变快了。DNS、获取连接、TCP 建连、TLS 认证、服务端处理和响应传输,各自有不同的计时边界。分析前应先确认哪些阶段真的执行过;单看一次请求总耗时,无法区分复用既有连接与新连接上的 TLS 会话恢复。 消息会按批次传输 TLS 握手虽然包含多条消息,但并不是每条消息都需要单独一次往返,多条消息可以在同一批次中发送。通常的 TLS 1.3 完整握手在普通客户端应用数据之前需要一次 TLS 往返,HelloRetryRequest 会再增加一次。TCP 建连和之后的应用请求、响应,还属于另外的成本。 不使用 early data 的普通 TLS 1.3 会话恢复,通常也需要一次 TLS 往返。它能减少证书传输与认证工作,却仍可能执行新的临时密钥交换;恢复时派生的是新流量密钥,不是原封不动继续使用旧加密密钥。可选零往返数据有额外的重放安全限制,不能把一次快速重复请求直接叫作零往返。 HTTP 连接复用则跳过了建立另一条连接的步骤,因此该请求没有新的 TLS 握手。HTTP/2 多路复用也可以让多个流共享已建立连接。相反,关闭空闲连接再重新连接,即使缓存票据允许 TLS 恢复,仍然需要重新完成传输层建立,不能把两种节省混成同一个动作。 坏掉的样子 正常请求若每次创建新的 transport,就会丢掉连接池。响应正文没有读完或关闭,也可能妨碍复用。空闲超时、入口变化或并发需求都可能增加新连接数量,因此在把延迟变化归因于证书算法之前,应先按真实复用情况和握手事件分类,确认自己比较的是同一种路径。 尝试恢复后仍发生完整握手,并不必然意味着服务故障。票据可能过期或被拒绝,客户端也可能没有可用缓存。应保留普通证书验证,并观察最终选择的行为;为赢得性能数字而减少认证检查,改变的是安全约定,不是在同一个操作下获得了免费优化。 计时名称也要有定义。首字节时间包含的不只有 TLS,总时间还包括消费响应内容。跟踪回调可能来自不同协程,收集器必须正确同步。应比较有代表性的分布及连接类别,不能拿几次回环请求的微秒数去预测公网加速,也不能把偶然更快的一次执行写成稳定性能承诺。 快速验证 保存为 latency_probe.go,使用 Go 1.23 或更新版本运行 go run latency_probe.go。实验固定使用经过验证的 TLS 1.3 和 HTTP/1.1,服务端为接受的 TCP 连接编号。第一个 transport 同时保留 HTTP 连接池和 TLS 会话缓存,另一个独立 transport 刻意不配置会话缓存。 ...

六月 28, 2027 · 3 分钟 · XB Field Notes