Improve internationalized domain name handling
This PR improves internationalized domain name handling according to rules of IDNA and based on the description in RFC 5280, section 7: https://datatracker.ietf.org/doc/html/rfc5280#section-7. Support for internationalized URI(s), so-called IRIs, still needs to be done.
This commit is contained in:
parent
512b8d6730
commit
9617edf0c2
5 changed files with 295 additions and 88 deletions
|
@ -35,7 +35,6 @@ const (
|
||||||
// https://signal.org/docs/specifications/xeddsa/#xeddsa and implemented by
|
// https://signal.org/docs/specifications/xeddsa/#xeddsa and implemented by
|
||||||
// go.step.sm/crypto/x25519.
|
// go.step.sm/crypto/x25519.
|
||||||
type Nebula struct {
|
type Nebula struct {
|
||||||
*base
|
|
||||||
ID string `json:"-"`
|
ID string `json:"-"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.step.sm/crypto/x509util"
|
"go.step.sm/crypto/x509util"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/net/idna"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NamePolicyReason int
|
type NamePolicyReason int
|
||||||
|
@ -23,6 +23,15 @@ const (
|
||||||
// doesn't permit a DNS or another type of SAN to be signed
|
// doesn't permit a DNS or another type of SAN to be signed
|
||||||
// (or otherwise used).
|
// (or otherwise used).
|
||||||
NotAuthorizedForThisName NamePolicyReason = iota
|
NotAuthorizedForThisName NamePolicyReason = iota
|
||||||
|
// CannotParseDomain is returned when an error occurs
|
||||||
|
// when parsing the domain part of SAN or subject.
|
||||||
|
CannotParseDomain
|
||||||
|
// CannotParseRFC822Name is returned when an error
|
||||||
|
// occurs when parsing an email address.
|
||||||
|
CannotParseRFC822Name
|
||||||
|
// CannotMatch is the type of error returned when
|
||||||
|
// an error happens when matching SAN types.
|
||||||
|
CannotMatchNameToConstraint
|
||||||
)
|
)
|
||||||
|
|
||||||
type NamePolicyError struct {
|
type NamePolicyError struct {
|
||||||
|
@ -31,16 +40,26 @@ type NamePolicyError struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e NamePolicyError) Error() string {
|
func (e NamePolicyError) Error() string {
|
||||||
if e.Reason == NotAuthorizedForThisName {
|
switch e.Reason {
|
||||||
|
case NotAuthorizedForThisName:
|
||||||
return "not authorized to sign for this name: " + e.Detail
|
return "not authorized to sign for this name: " + e.Detail
|
||||||
|
case CannotParseDomain:
|
||||||
|
return "cannot parse domain: " + e.Detail
|
||||||
|
case CannotParseRFC822Name:
|
||||||
|
return "cannot parse rfc822Name: " + e.Detail
|
||||||
|
case CannotMatchNameToConstraint:
|
||||||
|
return "error matching name to constraint: " + e.Detail
|
||||||
|
default:
|
||||||
|
return "unknown error: " + e.Detail
|
||||||
}
|
}
|
||||||
return "unknown error"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NamePolicyEngine can be used to check that a CSR or Certificate meets all allowed and
|
// NamePolicyEngine can be used to check that a CSR or Certificate meets all allowed and
|
||||||
// denied names before a CA creates and/or signs the Certificate.
|
// denied names before a CA creates and/or signs the Certificate.
|
||||||
// TODO(hs): the X509 RFC also defines name checks on directory name; support that?
|
// TODO(hs): the X509 RFC also defines name checks on directory name; support that?
|
||||||
// TODO(hs): implement Stringer interface: describe the contents of the NamePolicyEngine?
|
// TODO(hs): implement Stringer interface: describe the contents of the NamePolicyEngine?
|
||||||
|
// TODO(hs): implement matching URI schemes, paths, etc; not just the domain part of URI domains
|
||||||
|
|
||||||
type NamePolicyEngine struct {
|
type NamePolicyEngine struct {
|
||||||
|
|
||||||
// verifySubjectCommonName is set when Subject Common Name must be verified
|
// verifySubjectCommonName is set when Subject Common Name must be verified
|
||||||
|
@ -275,8 +294,6 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA
|
||||||
// this number as a total of all checks and keeps a (pointer to a) counter of the number of checks
|
// this number as a total of all checks and keeps a (pointer to a) counter of the number of checks
|
||||||
// executed so far.
|
// executed so far.
|
||||||
|
|
||||||
// TODO: implement matching URI schemes, paths, etc; not just the domain
|
|
||||||
|
|
||||||
// TODO: gather all errors, or return early? Currently we return early on the first wrong name; check might fail for multiple names.
|
// TODO: gather all errors, or return early? Currently we return early on the first wrong name; check might fail for multiple names.
|
||||||
// Perhaps make that an option?
|
// Perhaps make that an option?
|
||||||
for _, dns := range dnsNames {
|
for _, dns := range dnsNames {
|
||||||
|
@ -289,10 +306,28 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA
|
||||||
Detail: fmt.Sprintf("dns %q is not explicitly permitted by any constraint", dns),
|
Detail: fmt.Sprintf("dns %q is not explicitly permitted by any constraint", dns),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := domainToReverseLabels(dns); !ok {
|
didCutWildcard := false
|
||||||
return errors.Errorf("cannot parse dns %q", dns)
|
if strings.HasPrefix(dns, "*.") {
|
||||||
|
dns = dns[1:]
|
||||||
|
didCutWildcard = true
|
||||||
}
|
}
|
||||||
if err := checkNameConstraints("dns", dns, dns,
|
parsedDNS, err := idna.Lookup.ToASCII(dns)
|
||||||
|
if err != nil {
|
||||||
|
return NamePolicyError{
|
||||||
|
Reason: CannotParseDomain,
|
||||||
|
Detail: fmt.Sprintf("dns %q cannot be converted to ASCII", dns),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if didCutWildcard {
|
||||||
|
parsedDNS = "*" + parsedDNS
|
||||||
|
}
|
||||||
|
if _, ok := domainToReverseLabels(parsedDNS); !ok {
|
||||||
|
return NamePolicyError{
|
||||||
|
Reason: CannotParseDomain,
|
||||||
|
Detail: fmt.Sprintf("cannot parse dns %q", dns),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := checkNameConstraints("dns", dns, parsedDNS,
|
||||||
func(parsedName, constraint interface{}) (bool, error) {
|
func(parsedName, constraint interface{}) (bool, error) {
|
||||||
return e.matchDomainConstraint(parsedName.(string), constraint.(string))
|
return e.matchDomainConstraint(parsedName.(string), constraint.(string))
|
||||||
}, e.permittedDNSDomains, e.excludedDNSDomains); err != nil {
|
}, e.permittedDNSDomains, e.excludedDNSDomains); err != nil {
|
||||||
|
@ -324,8 +359,22 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA
|
||||||
}
|
}
|
||||||
mailbox, ok := parseRFC2821Mailbox(email)
|
mailbox, ok := parseRFC2821Mailbox(email)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot parse rfc822Name %q", mailbox)
|
return NamePolicyError{
|
||||||
|
Reason: CannotParseRFC822Name,
|
||||||
|
Detail: fmt.Sprintf("invalid rfc822Name %q", mailbox),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// According to RFC 5280, section 7.5, emails are considered to match if the local part is
|
||||||
|
// an exact match and the host (domain) part matches the ASCII representation (case-insensitive):
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc5280#section-7.5
|
||||||
|
domainASCII, err := idna.ToASCII(mailbox.domain)
|
||||||
|
if err != nil {
|
||||||
|
return NamePolicyError{
|
||||||
|
Reason: CannotParseDomain,
|
||||||
|
Detail: fmt.Sprintf("cannot parse email domain %q", email),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mailbox.domain = domainASCII
|
||||||
if err := checkNameConstraints("email", email, mailbox,
|
if err := checkNameConstraints("email", email, mailbox,
|
||||||
func(parsedName, constraint interface{}) (bool, error) {
|
func(parsedName, constraint interface{}) (bool, error) {
|
||||||
return e.matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string))
|
return e.matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string))
|
||||||
|
@ -334,6 +383,8 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(hs): fix internationalization for URIs (IRIs)
|
||||||
|
|
||||||
for _, uri := range uris {
|
for _, uri := range uris {
|
||||||
if e.numberOfURIDomainConstraints == 0 && e.totalNumberOfPermittedConstraints > 0 {
|
if e.numberOfURIDomainConstraints == 0 && e.totalNumberOfPermittedConstraints > 0 {
|
||||||
return NamePolicyError{
|
return NamePolicyError{
|
||||||
|
@ -365,12 +416,6 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(hs): when the error is not nil and returned up in the above, we can add
|
|
||||||
// additional context to it (i.e. the cert or csr that was inspected).
|
|
||||||
|
|
||||||
// TODO(hs): validate other types of SANs? The Go std library skips those.
|
|
||||||
// These could be custom checkers.
|
|
||||||
|
|
||||||
// if all checks out, all SANs are allowed
|
// if all checks out, all SANs are allowed
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -393,7 +438,7 @@ func checkNameConstraints(
|
||||||
match, err := match(parsedName, constraint)
|
match, err := match(parsedName, constraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NamePolicyError{
|
return NamePolicyError{
|
||||||
Reason: NotAuthorizedForThisName,
|
Reason: CannotMatchNameToConstraint,
|
||||||
Detail: err.Error(),
|
Detail: err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -414,7 +459,7 @@ func checkNameConstraints(
|
||||||
var err error
|
var err error
|
||||||
if ok, err = match(parsedName, constraint); err != nil {
|
if ok, err = match(parsedName, constraint); err != nil {
|
||||||
return NamePolicyError{
|
return NamePolicyError{
|
||||||
Reason: NotAuthorizedForThisName,
|
Reason: CannotMatchNameToConstraint,
|
||||||
Detail: err.Error(),
|
Detail: err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,16 +17,15 @@ import (
|
||||||
|
|
||||||
func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
engine *NamePolicyEngine
|
allowLiteralWildcardNames bool
|
||||||
domain string
|
domain string
|
||||||
constraint string
|
constraint string
|
||||||
want bool
|
want bool
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "fail/wildcard",
|
name: "fail/wildcard",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "host.local",
|
domain: "host.local",
|
||||||
constraint: ".example.com", // internally we're using the x509 period prefix as the indicator for exactly one subdomain
|
constraint: ".example.com", // internally we're using the x509 period prefix as the indicator for exactly one subdomain
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -34,7 +33,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/wildcard-literal",
|
name: "fail/wildcard-literal",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "*.example.com",
|
domain: "*.example.com",
|
||||||
constraint: ".example.com", // internally we're using the x509 period prefix as the indicator for exactly one subdomain
|
constraint: ".example.com", // internally we're using the x509 period prefix as the indicator for exactly one subdomain
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -42,7 +40,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/specific-domain",
|
name: "fail/specific-domain",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "www.example.com",
|
domain: "www.example.com",
|
||||||
constraint: "host.example.com",
|
constraint: "host.example.com",
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -50,7 +47,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/single-whitespace-domain",
|
name: "fail/single-whitespace-domain",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: " ",
|
domain: " ",
|
||||||
constraint: "host.example.com",
|
constraint: "host.example.com",
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -58,7 +54,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/period-domain",
|
name: "fail/period-domain",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: ".host.example.com",
|
domain: ".host.example.com",
|
||||||
constraint: ".example.com",
|
constraint: ".example.com",
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -66,7 +61,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/wrong-asterisk-prefix",
|
name: "fail/wrong-asterisk-prefix",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "*Xexample.com",
|
domain: "*Xexample.com",
|
||||||
constraint: ".example.com",
|
constraint: ".example.com",
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -74,7 +68,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/asterisk-in-domain",
|
name: "fail/asterisk-in-domain",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "e*ample.com",
|
domain: "e*ample.com",
|
||||||
constraint: ".com",
|
constraint: ".com",
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -82,7 +75,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/asterisk-label",
|
name: "fail/asterisk-label",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "example.*.local",
|
domain: "example.*.local",
|
||||||
constraint: ".local",
|
constraint: ".local",
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -90,7 +82,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/multiple-periods",
|
name: "fail/multiple-periods",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "example.local",
|
domain: "example.local",
|
||||||
constraint: "..local",
|
constraint: "..local",
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -98,23 +89,20 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/error-parsing-domain",
|
name: "fail/error-parsing-domain",
|
||||||
engine: &NamePolicyEngine{},
|
domain: string(byte(0)),
|
||||||
domain: string([]byte{0}),
|
|
||||||
constraint: ".local",
|
constraint: ".local",
|
||||||
want: false,
|
want: false,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/error-parsing-constraint",
|
name: "fail/error-parsing-constraint",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "example.local",
|
domain: "example.local",
|
||||||
constraint: string([]byte{0}),
|
constraint: string(byte(0)),
|
||||||
want: false,
|
want: false,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/no-subdomain",
|
name: "fail/no-subdomain",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "local",
|
domain: "local",
|
||||||
constraint: ".local",
|
constraint: ".local",
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -122,7 +110,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/too-many-subdomains",
|
name: "fail/too-many-subdomains",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "www.example.local",
|
domain: "www.example.local",
|
||||||
constraint: ".local",
|
constraint: ".local",
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -130,7 +117,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/wrong-domain",
|
name: "fail/wrong-domain",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "example.notlocal",
|
domain: "example.notlocal",
|
||||||
constraint: ".local",
|
constraint: ".local",
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -138,7 +124,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "false/idna-internationalized-domain-name",
|
name: "false/idna-internationalized-domain-name",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "JP納豆.例.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
domain: "JP納豆.例.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
||||||
constraint: ".例.jp",
|
constraint: ".例.jp",
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -146,7 +131,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "false/idna-internationalized-domain-name-constraint",
|
name: "false/idna-internationalized-domain-name-constraint",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "xn--jp-cd2fp15c.xn--fsq.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
domain: "xn--jp-cd2fp15c.xn--fsq.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
||||||
constraint: ".例.jp",
|
constraint: ".例.jp",
|
||||||
want: false,
|
want: false,
|
||||||
|
@ -154,7 +138,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ok/empty-constraint",
|
name: "ok/empty-constraint",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "www.example.com",
|
domain: "www.example.com",
|
||||||
constraint: "",
|
constraint: "",
|
||||||
want: true,
|
want: true,
|
||||||
|
@ -162,25 +145,21 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ok/wildcard",
|
name: "ok/wildcard",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "www.example.com",
|
domain: "www.example.com",
|
||||||
constraint: ".example.com", // internally we're using the x509 period prefix as the indicator for exactly one subdomain
|
constraint: ".example.com", // internally we're using the x509 period prefix as the indicator for exactly one subdomain
|
||||||
want: true,
|
want: true,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ok/wildcard-literal",
|
name: "ok/wildcard-literal",
|
||||||
engine: &NamePolicyEngine{
|
allowLiteralWildcardNames: true,
|
||||||
allowLiteralWildcardNames: true,
|
domain: "*.example.com", // specifically allowed using an option on the NamePolicyEngine
|
||||||
},
|
constraint: ".example.com", // internally we're using the x509 period prefix as the indicator for exactly one subdomain
|
||||||
domain: "*.example.com", // specifically allowed using an option on the NamePolicyEngine
|
want: true,
|
||||||
constraint: ".example.com", // internally we're using the x509 period prefix as the indicator for exactly one subdomain
|
wantErr: false,
|
||||||
want: true,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ok/specific-domain",
|
name: "ok/specific-domain",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "www.example.com",
|
domain: "www.example.com",
|
||||||
constraint: "www.example.com",
|
constraint: "www.example.com",
|
||||||
want: true,
|
want: true,
|
||||||
|
@ -188,7 +167,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ok/different-case",
|
name: "ok/different-case",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "WWW.EXAMPLE.com",
|
domain: "WWW.EXAMPLE.com",
|
||||||
constraint: "www.example.com",
|
constraint: "www.example.com",
|
||||||
want: true,
|
want: true,
|
||||||
|
@ -196,7 +174,6 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ok/idna-internationalized-domain-name-punycode",
|
name: "ok/idna-internationalized-domain-name-punycode",
|
||||||
engine: &NamePolicyEngine{},
|
|
||||||
domain: "xn--jp-cd2fp15c.xn--fsq.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
domain: "xn--jp-cd2fp15c.xn--fsq.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
||||||
constraint: ".xn--fsq.jp",
|
constraint: ".xn--fsq.jp",
|
||||||
want: true,
|
want: true,
|
||||||
|
@ -205,7 +182,10 @@ func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := tt.engine.matchDomainConstraint(tt.domain, tt.constraint)
|
engine := NamePolicyEngine{
|
||||||
|
allowLiteralWildcardNames: tt.allowLiteralWildcardNames,
|
||||||
|
}
|
||||||
|
got, err := engine.matchDomainConstraint(tt.domain, tt.constraint)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("NamePolicyEngine.matchDomainConstraint() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("NamePolicyEngine.matchDomainConstraint() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
@ -749,6 +729,19 @@ func TestNamePolicyEngine_X509_AllAllowed(t *testing.T) {
|
||||||
want: false,
|
want: false,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "fail/dns-permitted-idna-internationalized-domain",
|
||||||
|
options: []NamePolicyOption{
|
||||||
|
AddPermittedDNSDomain("*.豆.jp"),
|
||||||
|
},
|
||||||
|
cert: &x509.Certificate{
|
||||||
|
DNSNames: []string{
|
||||||
|
string(byte(0)) + ".例.jp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "fail/ipv4-permitted",
|
name: "fail/ipv4-permitted",
|
||||||
options: []NamePolicyOption{
|
options: []NamePolicyOption{
|
||||||
|
@ -837,6 +830,39 @@ func TestNamePolicyEngine_X509_AllAllowed(t *testing.T) {
|
||||||
want: false,
|
want: false,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "fail/mail-permitted-idna-internationalized-domain",
|
||||||
|
options: []NamePolicyOption{
|
||||||
|
AddPermittedEmailAddress("@例.jp"),
|
||||||
|
},
|
||||||
|
cert: &x509.Certificate{
|
||||||
|
EmailAddresses: []string{"bücher@例.jp"},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/mail-permitted-idna-internationalized-domain-rfc822",
|
||||||
|
options: []NamePolicyOption{
|
||||||
|
AddPermittedEmailAddress("@例.jp"),
|
||||||
|
},
|
||||||
|
cert: &x509.Certificate{
|
||||||
|
EmailAddresses: []string{"bücher@例.jp" + string(byte(0))},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/mail-permitted-idna-internationalized-domain-ascii",
|
||||||
|
options: []NamePolicyOption{
|
||||||
|
AddPermittedEmailAddress("@例.jp"),
|
||||||
|
},
|
||||||
|
cert: &x509.Certificate{
|
||||||
|
EmailAddresses: []string{"mail@xn---bla.jp"},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "fail/permitted-uri-domain-wildcard",
|
name: "fail/permitted-uri-domain-wildcard",
|
||||||
options: []NamePolicyOption{
|
options: []NamePolicyOption{
|
||||||
|
@ -1453,17 +1479,6 @@ func TestNamePolicyEngine_X509_AllAllowed(t *testing.T) {
|
||||||
want: true,
|
want: true,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "ok/empty-dns-constraint",
|
|
||||||
options: []NamePolicyOption{
|
|
||||||
AddPermittedDNSDomain(""),
|
|
||||||
},
|
|
||||||
cert: &x509.Certificate{
|
|
||||||
DNSNames: []string{"example.local"},
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "ok/dns-permitted-wildcard-literal",
|
name: "ok/dns-permitted-wildcard-literal",
|
||||||
options: []NamePolicyOption{
|
options: []NamePolicyOption{
|
||||||
|
@ -1497,6 +1512,19 @@ func TestNamePolicyEngine_X509_AllAllowed(t *testing.T) {
|
||||||
want: true,
|
want: true,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ok/dns-permitted-idna-internationalized-domain",
|
||||||
|
options: []NamePolicyOption{
|
||||||
|
AddPermittedDNSDomain("*.例.jp"),
|
||||||
|
},
|
||||||
|
cert: &x509.Certificate{
|
||||||
|
DNSNames: []string{
|
||||||
|
"JP納豆.例.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "ok/ipv4-permitted",
|
name: "ok/ipv4-permitted",
|
||||||
options: []NamePolicyOption{
|
options: []NamePolicyOption{
|
||||||
|
@ -1558,6 +1586,17 @@ func TestNamePolicyEngine_X509_AllAllowed(t *testing.T) {
|
||||||
want: true,
|
want: true,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ok/mail-permitted-idna-internationalized-domain",
|
||||||
|
options: []NamePolicyOption{
|
||||||
|
AddPermittedEmailAddress("@例.jp"),
|
||||||
|
},
|
||||||
|
cert: &x509.Certificate{
|
||||||
|
EmailAddresses: []string{},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "ok/uri-permitted-domain-wildcard",
|
name: "ok/uri-permitted-domain-wildcard",
|
||||||
options: []NamePolicyOption{
|
options: []NamePolicyOption{
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/idna"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NamePolicyOption func(e *NamePolicyEngine) error
|
type NamePolicyOption func(e *NamePolicyEngine) error
|
||||||
|
@ -592,14 +593,24 @@ func isIPv4(ip net.IP) bool {
|
||||||
|
|
||||||
func normalizeAndValidateDNSDomainConstraint(constraint string) (string, error) {
|
func normalizeAndValidateDNSDomainConstraint(constraint string) (string, error) {
|
||||||
normalizedConstraint := strings.ToLower(strings.TrimSpace(constraint))
|
normalizedConstraint := strings.ToLower(strings.TrimSpace(constraint))
|
||||||
|
if normalizedConstraint == "" {
|
||||||
|
return "", errors.Errorf("contraint %q can not be empty or white space string", constraint)
|
||||||
|
}
|
||||||
if strings.Contains(normalizedConstraint, "..") {
|
if strings.Contains(normalizedConstraint, "..") {
|
||||||
return "", errors.Errorf("domain constraint %q cannot have empty labels", constraint)
|
return "", errors.Errorf("domain constraint %q cannot have empty labels", constraint)
|
||||||
}
|
}
|
||||||
|
if normalizedConstraint[0] == '*' && normalizedConstraint[1] != '.' {
|
||||||
|
return "", errors.Errorf("wildcard character in domain constraint %q can only be used to match (full) labels", constraint)
|
||||||
|
}
|
||||||
|
if strings.LastIndex(normalizedConstraint, "*") > 0 {
|
||||||
|
return "", errors.Errorf("domain constraint %q can only have wildcard as starting character", constraint)
|
||||||
|
}
|
||||||
if strings.HasPrefix(normalizedConstraint, "*.") {
|
if strings.HasPrefix(normalizedConstraint, "*.") {
|
||||||
normalizedConstraint = normalizedConstraint[1:] // cut off wildcard character; keep the period
|
normalizedConstraint = normalizedConstraint[1:] // cut off wildcard character; keep the period
|
||||||
}
|
}
|
||||||
if strings.Contains(normalizedConstraint, "*") {
|
normalizedConstraint, err := idna.Lookup.ToASCII(normalizedConstraint)
|
||||||
return "", errors.Errorf("domain constraint %q can only have wildcard as starting character", constraint)
|
if err != nil {
|
||||||
|
return "", errors.Wrapf(err, "domain constraint %q can not be converted to ASCII", constraint)
|
||||||
}
|
}
|
||||||
if _, ok := domainToReverseLabels(normalizedConstraint); !ok {
|
if _, ok := domainToReverseLabels(normalizedConstraint); !ok {
|
||||||
return "", errors.Errorf("cannot parse domain constraint %q", constraint)
|
return "", errors.Errorf("cannot parse domain constraint %q", constraint)
|
||||||
|
@ -609,8 +620,11 @@ func normalizeAndValidateDNSDomainConstraint(constraint string) (string, error)
|
||||||
|
|
||||||
func normalizeAndValidateEmailConstraint(constraint string) (string, error) {
|
func normalizeAndValidateEmailConstraint(constraint string) (string, error) {
|
||||||
normalizedConstraint := strings.ToLower(strings.TrimSpace(constraint))
|
normalizedConstraint := strings.ToLower(strings.TrimSpace(constraint))
|
||||||
|
if normalizedConstraint == "" {
|
||||||
|
return "", errors.Errorf("email contraint %q can not be empty or white space string", constraint)
|
||||||
|
}
|
||||||
if strings.Contains(normalizedConstraint, "*") {
|
if strings.Contains(normalizedConstraint, "*") {
|
||||||
return "", fmt.Errorf("email constraint %q cannot contain asterisk", constraint)
|
return "", fmt.Errorf("email constraint %q cannot contain asterisk wildcard", constraint)
|
||||||
}
|
}
|
||||||
if strings.Count(normalizedConstraint, "@") > 1 {
|
if strings.Count(normalizedConstraint, "@") > 1 {
|
||||||
return "", fmt.Errorf("email constraint %q contains too many @ characters", constraint)
|
return "", fmt.Errorf("email constraint %q contains too many @ characters", constraint)
|
||||||
|
@ -622,8 +636,23 @@ func normalizeAndValidateEmailConstraint(constraint string) (string, error) {
|
||||||
return "", fmt.Errorf("email constraint %q cannot start with period", constraint)
|
return "", fmt.Errorf("email constraint %q cannot start with period", constraint)
|
||||||
}
|
}
|
||||||
if strings.Contains(normalizedConstraint, "@") {
|
if strings.Contains(normalizedConstraint, "@") {
|
||||||
if _, ok := parseRFC2821Mailbox(normalizedConstraint); !ok {
|
mailbox, ok := parseRFC2821Mailbox(normalizedConstraint)
|
||||||
return "", fmt.Errorf("cannot parse email constraint %q", constraint)
|
if !ok {
|
||||||
|
return "", fmt.Errorf("cannot parse email constraint %q as RFC 2821 mailbox", constraint)
|
||||||
|
}
|
||||||
|
// According to RFC 5280, section 7.5, emails are considered to match if the local part is
|
||||||
|
// an exact match and the host (domain) part matches the ASCII representation (case-insensitive):
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc5280#section-7.5
|
||||||
|
domainASCII, err := idna.Lookup.ToASCII(mailbox.domain)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrapf(err, "email constraint %q domain part %q cannot be converted to ASCII", constraint, mailbox.domain)
|
||||||
|
}
|
||||||
|
normalizedConstraint = mailbox.local + "@" + domainASCII
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
normalizedConstraint, err = idna.Lookup.ToASCII(normalizedConstraint)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrapf(err, "email constraint %q cannot be converted to ASCII", constraint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := domainToReverseLabels(normalizedConstraint); !ok {
|
if _, ok := domainToReverseLabels(normalizedConstraint); !ok {
|
||||||
|
@ -634,6 +663,9 @@ func normalizeAndValidateEmailConstraint(constraint string) (string, error) {
|
||||||
|
|
||||||
func normalizeAndValidateURIDomainConstraint(constraint string) (string, error) {
|
func normalizeAndValidateURIDomainConstraint(constraint string) (string, error) {
|
||||||
normalizedConstraint := strings.ToLower(strings.TrimSpace(constraint))
|
normalizedConstraint := strings.ToLower(strings.TrimSpace(constraint))
|
||||||
|
if normalizedConstraint == "" {
|
||||||
|
return "", errors.Errorf("URI domain contraint %q cannot be empty or white space string", constraint)
|
||||||
|
}
|
||||||
if strings.Contains(normalizedConstraint, "..") {
|
if strings.Contains(normalizedConstraint, "..") {
|
||||||
return "", errors.Errorf("URI domain constraint %q cannot have empty labels", constraint)
|
return "", errors.Errorf("URI domain constraint %q cannot have empty labels", constraint)
|
||||||
}
|
}
|
||||||
|
@ -643,7 +675,23 @@ func normalizeAndValidateURIDomainConstraint(constraint string) (string, error)
|
||||||
if strings.Contains(normalizedConstraint, "*") {
|
if strings.Contains(normalizedConstraint, "*") {
|
||||||
return "", errors.Errorf("URI domain constraint %q can only have wildcard as starting character", constraint)
|
return "", errors.Errorf("URI domain constraint %q can only have wildcard as starting character", constraint)
|
||||||
}
|
}
|
||||||
// TODO(hs): block constraints that look like IPs too? Because hosts can't be matched to those.
|
// we're being strict with square brackets in domains; we don't allow them, no matter what
|
||||||
|
if strings.Contains(normalizedConstraint, "[") || strings.Contains(normalizedConstraint, "]") {
|
||||||
|
return "", errors.Errorf("URI domain constraint %q contains invalid square brackets", constraint)
|
||||||
|
}
|
||||||
|
if _, _, err := net.SplitHostPort(normalizedConstraint); err == nil {
|
||||||
|
// a successful split (likely) with host and port; we don't currently allow ports in the config
|
||||||
|
return "", errors.Errorf("URI domain constraint %q cannot contain port", constraint)
|
||||||
|
}
|
||||||
|
// check if the host part of the URI domain constraint is an IP
|
||||||
|
if net.ParseIP(normalizedConstraint) != nil {
|
||||||
|
return "", errors.Errorf("URI domain constraint %q cannot be an IP", constraint)
|
||||||
|
}
|
||||||
|
// TODO(hs): verify that this is OK for URI (IRI) domains too
|
||||||
|
normalizedConstraint, err := idna.Lookup.ToASCII(normalizedConstraint)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrapf(err, "URI domain constraint %q cannot be converted to ASCII", constraint)
|
||||||
|
}
|
||||||
_, ok := domainToReverseLabels(normalizedConstraint)
|
_, ok := domainToReverseLabels(normalizedConstraint)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("cannot parse URI domain constraint %q", constraint)
|
return "", fmt.Errorf("cannot parse URI domain constraint %q", constraint)
|
||||||
|
|
|
@ -16,8 +16,20 @@ func Test_normalizeAndValidateDNSDomainConstraint(t *testing.T) {
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "fail/too-many-asterisks",
|
name: "fail/empty-constraint",
|
||||||
constraint: "**.local",
|
constraint: "",
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/wildcard-partial-label",
|
||||||
|
constraint: "*xxxx.local",
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/wildcard-in-the-middle",
|
||||||
|
constraint: "x.*.local",
|
||||||
want: "",
|
want: "",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
@ -34,14 +46,8 @@ func Test_normalizeAndValidateDNSDomainConstraint(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "false/idna-internationalized-domain-name",
|
name: "fail/idna-internationalized-domain-name-lookup",
|
||||||
constraint: ".例.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
constraint: `\00.local`, // invalid IDNA ASCII character
|
||||||
want: "",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "false/idna-internationalized-domain-name-constraint",
|
|
||||||
constraint: ".例.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
|
||||||
want: "",
|
want: "",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
@ -63,13 +69,18 @@ func Test_normalizeAndValidateDNSDomainConstraint(t *testing.T) {
|
||||||
want: ".xn--fsq.jp",
|
want: ".xn--fsq.jp",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ok/idna-internationalized-domain-name-lookup-transformed",
|
||||||
|
constraint: ".例.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
||||||
|
want: ".xn--fsq.jp",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := normalizeAndValidateDNSDomainConstraint(tt.constraint)
|
got, err := normalizeAndValidateDNSDomainConstraint(tt.constraint)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("normalizeAndValidateDNSDomainConstraint() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("normalizeAndValidateDNSDomainConstraint() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
t.Errorf("normalizeAndValidateDNSDomainConstraint() = %v, want %v", got, tt.want)
|
t.Errorf("normalizeAndValidateDNSDomainConstraint() = %v, want %v", got, tt.want)
|
||||||
|
@ -85,6 +96,12 @@ func Test_normalizeAndValidateEmailConstraint(t *testing.T) {
|
||||||
want string
|
want string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "fail/empty-constraint",
|
||||||
|
constraint: "",
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "fail/asterisk",
|
name: "fail/asterisk",
|
||||||
constraint: "*.local",
|
constraint: "*.local",
|
||||||
|
@ -111,13 +128,25 @@ func Test_normalizeAndValidateEmailConstraint(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/parse-mailbox",
|
name: "fail/parse-mailbox",
|
||||||
constraint: "mail@example.com" + string([]byte{0}),
|
constraint: "mail@example.com" + string(byte(0)),
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/idna-internationalized-domain",
|
||||||
|
constraint: `mail@xn--bla.local`,
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/idna-internationalized-domain-name-lookup",
|
||||||
|
constraint: `\00local`,
|
||||||
want: "",
|
want: "",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail/parse-domain",
|
name: "fail/parse-domain",
|
||||||
constraint: "example.com" + string([]byte{0}),
|
constraint: "x..example.com",
|
||||||
want: "",
|
want: "",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
@ -133,13 +162,19 @@ func Test_normalizeAndValidateEmailConstraint(t *testing.T) {
|
||||||
want: "mail@local",
|
want: "mail@local",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
// TODO(hs): fix the below; doesn't get past parseRFC2821Mailbox; I think it should be allowed.
|
||||||
|
// {
|
||||||
|
// name: "ok/idna-internationalized-local",
|
||||||
|
// constraint: `bücher@local`,
|
||||||
|
// want: "bücher@local",
|
||||||
|
// wantErr: false,
|
||||||
|
// },
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := normalizeAndValidateEmailConstraint(tt.constraint)
|
got, err := normalizeAndValidateEmailConstraint(tt.constraint)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("normalizeAndValidateEmailConstraint() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("normalizeAndValidateEmailConstraint() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
t.Errorf("normalizeAndValidateEmailConstraint() = %v, want %v", got, tt.want)
|
t.Errorf("normalizeAndValidateEmailConstraint() = %v, want %v", got, tt.want)
|
||||||
|
@ -155,6 +190,12 @@ func Test_normalizeAndValidateURIDomainConstraint(t *testing.T) {
|
||||||
want string
|
want string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "fail/empty-constraint",
|
||||||
|
constraint: "",
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "fail/too-many-asterisks",
|
name: "fail/too-many-asterisks",
|
||||||
constraint: "**.local",
|
constraint: "**.local",
|
||||||
|
@ -173,6 +214,42 @@ func Test_normalizeAndValidateURIDomainConstraint(t *testing.T) {
|
||||||
want: "",
|
want: "",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "fail/domain-with-port",
|
||||||
|
constraint: "host.local:8443",
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/ipv4",
|
||||||
|
constraint: "127.0.0.1",
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/ipv6-brackets",
|
||||||
|
constraint: "[::1]",
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/ipv6-no-brackets",
|
||||||
|
constraint: "::1",
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/ipv6-no-brackets",
|
||||||
|
constraint: "[::1",
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/idna-internationalized-domain-name-lookup",
|
||||||
|
constraint: `\00local`,
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "ok/wildcard",
|
name: "ok/wildcard",
|
||||||
constraint: "*.local",
|
constraint: "*.local",
|
||||||
|
@ -191,7 +268,6 @@ func Test_normalizeAndValidateURIDomainConstraint(t *testing.T) {
|
||||||
got, err := normalizeAndValidateURIDomainConstraint(tt.constraint)
|
got, err := normalizeAndValidateURIDomainConstraint(tt.constraint)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("normalizeAndValidateURIDomainConstraint() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("normalizeAndValidateURIDomainConstraint() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
t.Errorf("normalizeAndValidateURIDomainConstraint() = %v, want %v", got, tt.want)
|
t.Errorf("normalizeAndValidateURIDomainConstraint() = %v, want %v", got, tt.want)
|
||||||
|
|
Loading…
Reference in a new issue