Skip to content

Commit 4e9f76b

Browse files
committed
acme/autocert: add support for RFC 3492 to Manager.GetCertificate
1 parent a22ba07 commit 4e9f76b

File tree

4 files changed

+49
-13
lines changed

4 files changed

+49
-13
lines changed

Diff for: acme/autocert/autocert.go

+20-5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"time"
3333

3434
"golang.org/x/crypto/acme"
35+
"golang.org/x/net/idna"
3536
)
3637

3738
// createCertRetryAfter is how much time to wait before removing a failed state
@@ -63,12 +64,15 @@ type HostPolicy func(ctx context.Context, host string) error
6364
// Only exact matches are currently supported. Subdomains, regexp or wildcard
6465
// will not match.
6566
//
66-
// Note that all hosts will be converted to lowercase via strings.ToLower so that
67-
// Manager.GetCertificate can handle mixedcase hosts correctly.
67+
// Note that all hosts will be converted to Punycode via idna.Lookup.ToASCII so that
68+
// Manager.GetCertificate can handle the Unicode IDN and mixedcase hosts correctly.
69+
// Invlaid hosts will be silently ignored.
6870
func HostWhitelist(hosts ...string) HostPolicy {
6971
whitelist := make(map[string]bool, len(hosts))
7072
for _, h := range hosts {
71-
whitelist[strings.ToLower(h)] = true
73+
if h, err := idna.Lookup.ToASCII(h); err == nil {
74+
whitelist[h] = true
75+
}
7276
}
7377
return func(_ context.Context, host string) error {
7478
if !whitelist[host] {
@@ -239,14 +243,25 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
239243
return nil, errors.New("acme/autocert: Manager.Prompt not set")
240244
}
241245

242-
name := strings.ToLower(hello.ServerName)
246+
name := hello.ServerName
243247
if name == "" {
244248
return nil, errors.New("acme/autocert: missing server name")
245249
}
246250
if !strings.Contains(strings.Trim(name, "."), ".") {
247251
return nil, errors.New("acme/autocert: server name component count invalid")
248252
}
249-
if strings.ContainsAny(name, `+/\`) {
253+
254+
// Note that this conversion is necessary because some server names in the handshakes
255+
// made by some clients (like cURL) is not implicitly converted to Punycode, which will
256+
// cause the certificate to fail to be obtained. In addition, we should also treat
257+
// example.com and EXAMPLE.COM as equivalent and must return the same certificate for
258+
// them. Fortunately, this conversion also helped us deal with this kind of mixedcase
259+
// problems.
260+
//
261+
// Due to the "σςΣ" problem (see https://unicode.org/faq/idn.html#22), we can't use
262+
// idna.Punycode.ToASCII (or just idna.ToASCII) here.
263+
name, err := idna.Lookup.ToASCII(hello.ServerName)
264+
if err != nil {
250265
return nil, errors.New("acme/autocert: server name contains invalid character")
251266
}
252267

Diff for: acme/autocert/autocert_test.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -209,15 +209,26 @@ func TestGetCertificate_trailingDot(t *testing.T) {
209209
testGetCertificate(t, man, "example.org", hello)
210210
}
211211

212+
func TestGetCertificate_unicodeIDN(t *testing.T) {
213+
man := &Manager{Prompt: AcceptTOS}
214+
defer man.stopRenew()
215+
216+
hello := clientHelloInfo("σσσ.com", true)
217+
testGetCertificate(t, man, "xn--4xaaa.com", hello)
218+
219+
hello = clientHelloInfo("σςΣ.com", true)
220+
testGetCertificate(t, man, "xn--4xaaa.com", hello)
221+
}
222+
212223
func TestGetCertificate_mixedcase(t *testing.T) {
213224
man := &Manager{Prompt: AcceptTOS}
214225
defer man.stopRenew()
215226

216-
lowercaseHello := clientHelloInfo("example.org", true)
217-
testGetCertificate(t, man, "example.org", lowercaseHello)
227+
hello := clientHelloInfo("example.org", true)
228+
testGetCertificate(t, man, "example.org", hello)
218229

219-
uppercaseHello := clientHelloInfo("EXAMPLE.ORG", true)
220-
testGetCertificate(t, man, "example.org", uppercaseHello)
230+
hello = clientHelloInfo("EXAMPLE.ORG", true)
231+
testGetCertificate(t, man, "example.org", hello)
221232
}
222233

223234
func TestGetCertificate_ForceRSA(t *testing.T) {
@@ -917,13 +928,14 @@ func TestCache(t *testing.T) {
917928
}
918929

919930
func TestHostWhitelist(t *testing.T) {
920-
policy := HostWhitelist("example.com", "EXAMPLE.ORG", "*.example.net")
931+
policy := HostWhitelist("example.com", "EXAMPLE.ORG", "*.example.net", "σςΣ.com")
921932
tt := []struct {
922933
host string
923934
allow bool
924935
}{
925936
{"example.com", true},
926937
{"example.org", true},
938+
{"xn--4xaaa.com", true},
927939
{"one.example.com", false},
928940
{"two.example.org", false},
929941
{"three.example.net", false},

Diff for: go.mod

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
module golang.org/x/crypto
22

3-
require golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e
3+
require (
4+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
5+
golang.org/x/sys v0.0.0-20190412213103-97732733099d
6+
)

Diff for: go.sum

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
1-
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
2-
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
2+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
3+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
4+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
5+
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
6+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
7+
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
8+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

0 commit comments

Comments
 (0)