Недавно Let's Encrypt поделилась этой ошибкой, которая произошла в их системах и приводила к проблемам с сертификатами для их клиентов. Они описывают ошибку так:
Ошибка: когда запрос сертификата содержал N доменных имен, которые требовали перепроверки CAA, Боулдер выбирал одно доменное имя и проверял его N раз. На практике это означает, что если подписчик проверил доменное имя в момент X, а записи CAA для этого домена в момент X позволили выпуск Let's Encrypt, этот подписчик сможет выпустить сертификат, содержащий это доменное имя, до X + 30 дней, даже если позже кто-то установил на это доменное имя записи CAA, которые запрещают выпуск Let's Encrypt.
Означает ли это, что, когда у пользователя было несколько доменных имен, требующих повторной проверки CA, Let's Encrypt проверит только первый домен? Была ли проблема в том, что сертификаты были выпущены на доменах, которые не принадлежали пользователю, получавшему сертификат?
Let's Encrypt выдал менее безопасные сертификаты из-за ошибки. Это скорее состояние гонки, чем что-либо еще.
А CAA record - это дополнительная запись DNS, которая ограничивает поставщиков сертификатов, которым разрешено выдавать сертификаты для домена. Итак, если подписчик проверяет доменное имя во время X
и записи CAA для домена разрешили выпуск Let's Encrypt в то время, когда подписчик проверял свой домен, у подписчика будет 30 дней с даты проверки для выдачи действительных сертификатов. Таким образом, если кто-то позже добавит записи CAA, запрещающие выдачу сертификатов LE в любое время между временем X + 30 дней, подписчик сможет выдавать сертификаты, игнорируя запись CAA.
Если вы посмотрите на PR с ошибкой, здесь она впервые сработала, когда NewAuthorizationSchema
флаг функции был включен в продукте.
Код проблемы - это Общая ошибка в Go см. соответствующий код для боулдер-ра:
// 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) определяет, что данное полное доменное имя требует повторной проверки CAA, и использует поле идентификатора из объекта авторизации, которое было неверным из-за вышеуказанной фиксации. Таким образом, способ обработки поля идентификатора будет одинаковым для всех значений на одной карте. Так, например, если у вас было несколько авторизаций, требующих повторной проверки CAA, boulder-ra перепроверил бы только одно полное доменное имя, а не остальные.
Действительно плохая ошибка.