2022-09-20 02:45:13 +00:00
|
|
|
package constraints
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/x509"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/url"
|
|
|
|
)
|
|
|
|
|
|
|
|
var oidExtensionNameConstraints = []int{2, 5, 29, 30}
|
|
|
|
|
2022-09-20 18:33:36 +00:00
|
|
|
// ConstraintError is the typed error that will be returned if a constraint
|
|
|
|
// error is found.
|
2022-09-20 02:45:13 +00:00
|
|
|
type ConstraintError struct {
|
2022-09-20 17:36:44 +00:00
|
|
|
Type string
|
|
|
|
Name string
|
|
|
|
Detail string
|
2022-09-20 02:45:13 +00:00
|
|
|
}
|
|
|
|
|
2022-09-20 18:33:36 +00:00
|
|
|
// Error implements the error interface.
|
2022-09-20 02:45:13 +00:00
|
|
|
func (e ConstraintError) Error() string {
|
2022-09-20 17:36:44 +00:00
|
|
|
return e.Detail
|
2022-09-20 02:45:13 +00:00
|
|
|
}
|
|
|
|
|
2022-09-20 18:38:32 +00:00
|
|
|
// Engine implements a constraint validator for DNS names, IP addresses, Email
|
2022-09-20 18:36:45 +00:00
|
|
|
// addresses and URIs.
|
2022-09-20 18:38:32 +00:00
|
|
|
type Engine struct {
|
2022-09-20 02:45:13 +00:00
|
|
|
hasNameConstraints bool
|
|
|
|
permittedDNSDomains []string
|
|
|
|
excludedDNSDomains []string
|
|
|
|
permittedIPRanges []*net.IPNet
|
|
|
|
excludedIPRanges []*net.IPNet
|
|
|
|
permittedEmailAddresses []string
|
|
|
|
excludedEmailAddresses []string
|
|
|
|
permittedURIDomains []string
|
|
|
|
excludedURIDomains []string
|
|
|
|
}
|
|
|
|
|
2022-09-20 18:38:32 +00:00
|
|
|
// New creates a constraint validation engine that contains the given chain of
|
2022-09-20 18:33:36 +00:00
|
|
|
// certificates.
|
2022-09-20 18:38:32 +00:00
|
|
|
func New(chain ...*x509.Certificate) *Engine {
|
|
|
|
s := new(Engine)
|
2022-09-20 02:45:13 +00:00
|
|
|
for _, crt := range chain {
|
|
|
|
s.permittedDNSDomains = append(s.permittedDNSDomains, crt.PermittedDNSDomains...)
|
|
|
|
s.excludedDNSDomains = append(s.excludedDNSDomains, crt.ExcludedDNSDomains...)
|
|
|
|
s.permittedIPRanges = append(s.permittedIPRanges, crt.PermittedIPRanges...)
|
|
|
|
s.excludedIPRanges = append(s.excludedIPRanges, crt.ExcludedIPRanges...)
|
|
|
|
s.permittedEmailAddresses = append(s.permittedEmailAddresses, crt.PermittedEmailAddresses...)
|
|
|
|
s.excludedEmailAddresses = append(s.excludedEmailAddresses, crt.ExcludedEmailAddresses...)
|
|
|
|
s.permittedURIDomains = append(s.permittedURIDomains, crt.PermittedURIDomains...)
|
|
|
|
s.excludedURIDomains = append(s.excludedURIDomains, crt.ExcludedURIDomains...)
|
|
|
|
|
|
|
|
if !s.hasNameConstraints {
|
|
|
|
for _, ext := range crt.Extensions {
|
|
|
|
if ext.Id.Equal(oidExtensionNameConstraints) {
|
|
|
|
s.hasNameConstraints = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2022-09-20 18:33:36 +00:00
|
|
|
// Validate checks the given names with the name constraints defined in the
|
|
|
|
// service.
|
2022-09-20 18:38:32 +00:00
|
|
|
func (s *Engine) Validate(dnsNames []string, ipAddresses []net.IP, emailAddresses []string, uris []*url.URL) error {
|
2022-09-20 19:33:03 +00:00
|
|
|
if s == nil || !s.hasNameConstraints {
|
2022-09-20 02:45:13 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, name := range dnsNames {
|
|
|
|
if err := checkNameConstraints("DNS name", name, name, s.permittedDNSDomains, s.excludedDNSDomains,
|
|
|
|
func(parsedName, constraint any) (bool, error) {
|
|
|
|
return matchDomainConstraint(parsedName.(string), constraint.(string))
|
|
|
|
},
|
|
|
|
); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, ip := range ipAddresses {
|
|
|
|
if err := checkNameConstraints("IP address", ip.String(), ip, s.permittedIPRanges, s.excludedIPRanges,
|
|
|
|
func(parsedName, constraint any) (bool, error) {
|
|
|
|
return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet))
|
2022-09-20 17:36:44 +00:00
|
|
|
},
|
|
|
|
); err != nil {
|
2022-09-20 02:45:13 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, email := range emailAddresses {
|
|
|
|
mailbox, ok := parseRFC2821Mailbox(email)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("cannot parse rfc822Name %q", email)
|
|
|
|
}
|
|
|
|
if err := checkNameConstraints("Email address", email, mailbox, s.permittedEmailAddresses, s.excludedEmailAddresses,
|
|
|
|
func(parsedName, constraint any) (bool, error) {
|
|
|
|
return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string))
|
|
|
|
},
|
|
|
|
); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, uri := range uris {
|
|
|
|
if err := checkNameConstraints("URI", uri.String(), uri, s.permittedURIDomains, s.excludedURIDomains,
|
|
|
|
func(parsedName, constraint any) (bool, error) {
|
|
|
|
return matchURIConstraint(parsedName.(*url.URL), constraint.(string))
|
2022-09-20 17:36:44 +00:00
|
|
|
},
|
|
|
|
); err != nil {
|
2022-09-20 02:45:13 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2022-09-20 20:12:34 +00:00
|
|
|
|
|
|
|
// ValidateCertificate validates the DNS names, IP addresses, Email address and
|
|
|
|
// URIs present in the given certificate.
|
|
|
|
func (s *Engine) ValidateCertificate(cert *x509.Certificate) error {
|
|
|
|
return s.Validate(cert.DNSNames, cert.IPAddresses, cert.EmailAddresses, cert.URIs)
|
|
|
|
}
|