Skip to content

Commit e6cff69

Browse files
rolandshoemakergopherbot
authored andcommitted
crypto/x509: move constraint checking after chain building
The standard approach to constraint checking involves checking the constraints during chain building. This is typically done as most chain building algorithms want to find a single chain. We don't do this, and instead build every valid chain we can find. Because of this, we don't _need_ to do constraint checking during the chain building stage, and instead can defer it until we have built all of the potentially valid chains (we already do this for EKU nesting and policy checking). This allows us to limit the constraints we check to only chains issued by trusted roots, which reduces the attack surface for constraint checking, which is an annoyingly algorithmically complex process (for now). To maintain previous behavior, if we see an error during constraint checking, and we end up with no valid chains, we return the first constraint checking error, instead of a more verbose error indicating if there were different problems during filtering. At some point we probably should come up with a more unified error type for chain building that can contain information about multiple failure modes. Change-Id: I5780b3adce8538eb4c3b56ddec52f0723d39009e Reviewed-on: https://go-review.googlesource.com/c/go/+/713240 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Auto-Submit: Roland Shoemaker <[email protected]> Reviewed-by: Daniel McCarney <[email protected]>
1 parent f5f69a3 commit e6cff69

File tree

1 file changed

+112
-101
lines changed

1 file changed

+112
-101
lines changed

src/crypto/x509/verify.go

Lines changed: 112 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -636,107 +636,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
636636
}
637637
}
638638

639-
maxConstraintComparisons := opts.MaxConstraintComparisions
640-
if maxConstraintComparisons == 0 {
641-
maxConstraintComparisons = 250000
642-
}
643-
comparisonCount := 0
644-
645-
if certType == intermediateCertificate || certType == rootCertificate {
646-
if len(currentChain) == 0 {
647-
return errors.New("x509: internal error: empty chain when appending CA cert")
648-
}
649-
}
650-
651-
// Each time we do constraint checking, we need to check the constraints in
652-
// the current certificate against all of the names that preceded it. We
653-
// reverse these names using domainToReverseLabels, which is a relatively
654-
// expensive operation. Since we check each name against each constraint,
655-
// this requires us to do N*C calls to domainToReverseLabels (where N is the
656-
// total number of names that preceed the certificate, and C is the total
657-
// number of constraints in the certificate). By caching the results of
658-
// calling domainToReverseLabels, we can reduce that to N+C calls at the
659-
// cost of keeping all of the parsed names and constraints in memory until
660-
// we return from isValid.
661-
reversedDomainsCache := map[string][]string{}
662-
reversedConstraintsCache := map[string][]string{}
663-
664-
if (certType == intermediateCertificate || certType == rootCertificate) &&
665-
c.hasNameConstraints() {
666-
toCheck := []*Certificate{}
667-
for _, c := range currentChain {
668-
if c.hasSANExtension() {
669-
toCheck = append(toCheck, c)
670-
}
671-
}
672-
for _, sanCert := range toCheck {
673-
err := forEachSAN(sanCert.getSANExtension(), func(tag int, data []byte) error {
674-
switch tag {
675-
case nameTypeEmail:
676-
name := string(data)
677-
mailbox, ok := parseRFC2821Mailbox(name)
678-
if !ok {
679-
return fmt.Errorf("x509: cannot parse rfc822Name %q", mailbox)
680-
}
681-
682-
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox,
683-
func(parsedName, constraint any) (bool, error) {
684-
return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), reversedDomainsCache, reversedConstraintsCache)
685-
}, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil {
686-
return err
687-
}
688-
689-
case nameTypeDNS:
690-
name := string(data)
691-
if !domainNameValid(name, false) {
692-
return fmt.Errorf("x509: cannot parse dnsName %q", name)
693-
}
694-
695-
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name,
696-
func(parsedName, constraint any) (bool, error) {
697-
return matchDomainConstraint(parsedName.(string), constraint.(string), reversedDomainsCache, reversedConstraintsCache)
698-
}, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil {
699-
return err
700-
}
701-
702-
case nameTypeURI:
703-
name := string(data)
704-
uri, err := url.Parse(name)
705-
if err != nil {
706-
return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", name)
707-
}
708-
709-
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri,
710-
func(parsedName, constraint any) (bool, error) {
711-
return matchURIConstraint(parsedName.(*url.URL), constraint.(string), reversedDomainsCache, reversedConstraintsCache)
712-
}, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil {
713-
return err
714-
}
715-
716-
case nameTypeIP:
717-
ip := net.IP(data)
718-
if l := len(ip); l != net.IPv4len && l != net.IPv6len {
719-
return fmt.Errorf("x509: internal error: IP SAN %x failed to parse", data)
720-
}
721-
722-
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip,
723-
func(parsedName, constraint any) (bool, error) {
724-
return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet))
725-
}, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil {
726-
return err
727-
}
728-
729-
default:
730-
// Unknown SAN types are ignored.
731-
}
732-
733-
return nil
734-
})
735-
736-
if err != nil {
737-
return err
738-
}
739-
}
639+
if (certType == intermediateCertificate || certType == rootCertificate) && len(currentChain) == 0 {
640+
return errors.New("x509: internal error: empty chain when appending CA cert")
740641
}
741642

742643
// KeyUsage status flags are ignored. From Engineering Security, Peter
@@ -881,6 +782,7 @@ func (c *Certificate) Verify(opts VerifyOptions) ([][]*Certificate, error) {
881782

882783
var invalidPoliciesChains int
883784
var incompatibleKeyUsageChains int
785+
var constraintsHintErr error
884786
candidateChains = slices.DeleteFunc(candidateChains, func(chain []*Certificate) bool {
885787
if !policiesValid(chain, opts) {
886788
invalidPoliciesChains++
@@ -892,10 +794,19 @@ func (c *Certificate) Verify(opts VerifyOptions) ([][]*Certificate, error) {
892794
incompatibleKeyUsageChains++
893795
return true
894796
}
797+
if err := checkChainConstraints(chain, opts); err != nil {
798+
if constraintsHintErr == nil {
799+
constraintsHintErr = err
800+
}
801+
return true
802+
}
895803
return false
896804
})
897805

898806
if len(candidateChains) == 0 {
807+
if constraintsHintErr != nil {
808+
return nil, constraintsHintErr // Preserve previous constraint behavior
809+
}
899810
var details []string
900811
if incompatibleKeyUsageChains > 0 {
901812
if invalidPoliciesChains == 0 {
@@ -1271,6 +1182,106 @@ NextCert:
12711182
return true
12721183
}
12731184

1185+
func checkChainConstraints(chain []*Certificate, opts VerifyOptions) error {
1186+
maxConstraintComparisons := opts.MaxConstraintComparisions
1187+
if maxConstraintComparisons == 0 {
1188+
maxConstraintComparisons = 250000
1189+
}
1190+
comparisonCount := 0
1191+
1192+
// Each time we do constraint checking, we need to check the constraints in
1193+
// the current certificate against all of the names that preceded it. We
1194+
// reverse these names using domainToReverseLabels, which is a relatively
1195+
// expensive operation. Since we check each name against each constraint,
1196+
// this requires us to do N*C calls to domainToReverseLabels (where N is the
1197+
// total number of names that preceed the certificate, and C is the total
1198+
// number of constraints in the certificate). By caching the results of
1199+
// calling domainToReverseLabels, we can reduce that to N+C calls at the
1200+
// cost of keeping all of the parsed names and constraints in memory until
1201+
// we return from isValid.
1202+
reversedDomainsCache := map[string][]string{}
1203+
reversedConstraintsCache := map[string][]string{}
1204+
1205+
for i, c := range chain {
1206+
if !c.hasNameConstraints() {
1207+
continue
1208+
}
1209+
for _, sanCert := range chain[:i] {
1210+
if !sanCert.hasSANExtension() {
1211+
continue
1212+
}
1213+
err := forEachSAN(sanCert.getSANExtension(), func(tag int, data []byte) error {
1214+
switch tag {
1215+
case nameTypeEmail:
1216+
name := string(data)
1217+
mailbox, ok := parseRFC2821Mailbox(name)
1218+
if !ok {
1219+
return fmt.Errorf("x509: cannot parse rfc822Name %q", mailbox)
1220+
}
1221+
1222+
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox,
1223+
func(parsedName, constraint any) (bool, error) {
1224+
return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), reversedDomainsCache, reversedConstraintsCache)
1225+
}, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil {
1226+
return err
1227+
}
1228+
1229+
case nameTypeDNS:
1230+
name := string(data)
1231+
if !domainNameValid(name, false) {
1232+
return fmt.Errorf("x509: cannot parse dnsName %q", name)
1233+
}
1234+
1235+
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name,
1236+
func(parsedName, constraint any) (bool, error) {
1237+
return matchDomainConstraint(parsedName.(string), constraint.(string), reversedDomainsCache, reversedConstraintsCache)
1238+
}, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil {
1239+
return err
1240+
}
1241+
1242+
case nameTypeURI:
1243+
name := string(data)
1244+
uri, err := url.Parse(name)
1245+
if err != nil {
1246+
return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", name)
1247+
}
1248+
1249+
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri,
1250+
func(parsedName, constraint any) (bool, error) {
1251+
return matchURIConstraint(parsedName.(*url.URL), constraint.(string), reversedDomainsCache, reversedConstraintsCache)
1252+
}, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil {
1253+
return err
1254+
}
1255+
1256+
case nameTypeIP:
1257+
ip := net.IP(data)
1258+
if l := len(ip); l != net.IPv4len && l != net.IPv6len {
1259+
return fmt.Errorf("x509: internal error: IP SAN %x failed to parse", data)
1260+
}
1261+
1262+
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip,
1263+
func(parsedName, constraint any) (bool, error) {
1264+
return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet))
1265+
}, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil {
1266+
return err
1267+
}
1268+
1269+
default:
1270+
// Unknown SAN types are ignored.
1271+
}
1272+
1273+
return nil
1274+
})
1275+
1276+
if err != nil {
1277+
return err
1278+
}
1279+
}
1280+
}
1281+
1282+
return nil
1283+
}
1284+
12741285
func mustNewOIDFromInts(ints []uint64) OID {
12751286
oid, err := OIDFromInts(ints)
12761287
if err != nil {

0 commit comments

Comments
 (0)