最近,Let's Encrypt 分享了他们系统中发生的一个错误,该错误导致其客户端出现证书问题。他们这样描述这个错误:
错误:当证书请求包含 N 个需要重新检查 CAA 的域名时,Boulder 会选择一个域名并检查 N 次。这在实践中意味着,如果订户在时间 X 验证了域名,并且该域名在时间 X 的 CAA 记录允许 Let's Encrypt 颁发证书,则该订户将能够在 X+30 天内颁发包含该域名的证书,即使后来有人在该域名上安装了禁止 Let's Encrypt 颁发证书的 CAA 记录。
这是否意味着当用户有多个域名需要 CA 重新检查时,Let's Encrypt 只会检查第一个域名?问题是否在于证书颁发给不属于获得证书的用户的域名?
答案1
Let's Encrypt 根据该漏洞颁发了安全性较低的证书。这更像是一种竞争条件,而不是其他原因。
A加拿大航空协会记录是可选的 DNS 记录,用于限制哪些证书提供商可以为域颁发证书。因此,如果订户在某个时间验证域名,X
并且该域的 CAA 记录允许在订户验证其域期间颁发 Let's Encrypt 证书,则订户将有 30 天的时间(从验证日期起)颁发有效证书。因此,如果有人后来添加了 CAA 记录,禁止在 X + 30 天之间的任何时间颁发 LE 证书,则订户将能够忽略 CAA 记录颁发证书。
如果你看看公共关系NewAuthorizationSchema
有这个错误,这是在产品中启用功能标志时首次触发它的地方。
问题代码是常见的错误在 Go 中,查看 boulder-ra 的相关代码:
// authz2ModelMapToPB converts a mapping of domain name to authz2Models into a
// protobuf authorizations map
func authz2ModelMapToPB(m map[string]authz2Model) (*sapb.Authorizations, error) {
resp := &sapb.Authorizations{}
for k, v := range m {
// Make a copy of k because it will be reassigned with each loop.
kCopy := k
authzPB, err := modelToAuthzPB(&v)
if err != nil {
return nil, err
}
resp.Authz = append(resp.Authz, &sapb.Authorizations_MapElement{Domain: &kCopy, Authz: authzPB})
}
return resp, nil
}
问题在于:引用循环迭代器变量,它们未能v
正确处理第二个循环迭代器变量。
反过来,没有考虑这个函数中的两个重要字段IdentifierValue
:RegistrationID
func modelToAuthzPB(am *authzModel) (*corepb.Authorization, error) {
expires := am.Expires.UTC().UnixNano()
id := fmt.Sprintf("%d", am.ID)
status := uintToStatus[am.Status]
pb := &corepb.Authorization{
Id: &id,
Status: &status,
Identifier: &am.IdentifierValue,
RegistrationID: &am.RegistrationID,
Expires: &expires,
}
博尔德(特别是 boulder-ra)确定给定的 FQDN 需要 CAA 重新检查,并使用授权对象中的标识符字段,由于上述提交,该字段不正确。因此,标识符字段的处理方式对于单个映射中的所有值都是相同的。因此,例如,如果您有多个需要 CAA 重新检查的授权,boulder-ra 将仅重新检查一个 FQDN,而不会重新检查其他 FQDN。
确实是个糟糕的虫子。