diff --git a/acme/autocert/autocert.go b/acme/autocert/autocert.go index a50d9bf..e562609 100644 --- a/acme/autocert/autocert.go +++ b/acme/autocert/autocert.go @@ -32,6 +32,7 @@ import ( "time" "golang.org/x/crypto/acme" + "golang.org/x/net/idna" ) // createCertRetryAfter is how much time to wait before removing a failed state @@ -62,10 +63,16 @@ type HostPolicy func(ctx context.Context, host string) error // HostWhitelist returns a policy where only the specified host names are allowed. // Only exact matches are currently supported. Subdomains, regexp or wildcard // will not match. +// +// Note that all hosts will be converted to Punycode via idna.Lookup.ToASCII so that +// Manager.GetCertificate can handle the Unicode IDN and mixedcase hosts correctly. +// Invalid hosts will be silently ignored. func HostWhitelist(hosts ...string) HostPolicy { whitelist := make(map[string]bool, len(hosts)) for _, h := range hosts { - whitelist[h] = true + if h, err := idna.Lookup.ToASCII(h); err == nil { + whitelist[h] = true + } } return func(_ context.Context, host string) error { if !whitelist[host] { @@ -243,7 +250,17 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, if !strings.Contains(strings.Trim(name, "."), ".") { return nil, errors.New("acme/autocert: server name component count invalid") } - if strings.ContainsAny(name, `+/\`) { + + // Note that this conversion is necessary because some server names in the handshakes + // started by some clients (such as cURL) are not converted to Punycode, which will + // prevent us from obtaining certificates for them. In addition, we should also treat + // example.com and EXAMPLE.COM as equivalent and return the same certificate for them. + // Fortunately, this conversion also helped us deal with this kind of mixedcase problems. + // + // Due to the "σςΣ" problem (see https://unicode.org/faq/idn.html#22), we can't use + // idna.Punycode.ToASCII (or just idna.ToASCII) here. + name, err := idna.Lookup.ToASCII(name) + if err != nil { return nil, errors.New("acme/autocert: server name contains invalid character") } diff --git a/acme/autocert/autocert_test.go b/acme/autocert/autocert_test.go index 95e12e1..91f47e5 100644 --- a/acme/autocert/autocert_test.go +++ b/acme/autocert/autocert_test.go @@ -209,6 +209,28 @@ func TestGetCertificate_trailingDot(t *testing.T) { testGetCertificate(t, man, "example.org", hello) } +func TestGetCertificate_unicodeIDN(t *testing.T) { + man := &Manager{Prompt: AcceptTOS} + defer man.stopRenew() + + hello := clientHelloInfo("σσσ.com", true) + testGetCertificate(t, man, "xn--4xaaa.com", hello) + + hello = clientHelloInfo("σςΣ.com", true) + testGetCertificate(t, man, "xn--4xaaa.com", hello) +} + +func TestGetCertificate_mixedcase(t *testing.T) { + man := &Manager{Prompt: AcceptTOS} + defer man.stopRenew() + + hello := clientHelloInfo("example.org", true) + testGetCertificate(t, man, "example.org", hello) + + hello = clientHelloInfo("EXAMPLE.ORG", true) + testGetCertificate(t, man, "example.org", hello) +} + func TestGetCertificate_ForceRSA(t *testing.T) { man := &Manager{ Prompt: AcceptTOS, @@ -906,13 +928,14 @@ func TestCache(t *testing.T) { } func TestHostWhitelist(t *testing.T) { - policy := HostWhitelist("example.com", "example.org", "*.example.net") + policy := HostWhitelist("example.com", "EXAMPLE.ORG", "*.example.net", "σςΣ.com") tt := []struct { host string allow bool }{ {"example.com", true}, {"example.org", true}, + {"xn--4xaaa.com", true}, {"one.example.com", false}, {"two.example.org", false}, {"three.example.net", false}, diff --git a/go.mod b/go.mod index d2b06ca..c4af3ec 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,6 @@ module golang.org/x/crypto -require golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e +require ( + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 + golang.org/x/sys v0.0.0-20190412213103-97732733099d +) diff --git a/go.sum b/go.sum index be20559..44da498 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,8 @@ -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=