Skip to content

Commit f856061

Browse files
aofeibradfitz
authored andcommitted
acme/autocert: make host of TLS certificate to be obtained always Punycode
According to the [RFC 5280, section 4.2.1.6](https://tools.ietf.org/html/rfc5280#section-4.2.1.6): > ... > When the subjectAltName extension contains a domain name system > label, the domain name MUST be stored in the dNSName (an IA5String). > The name MUST be in the "preferred name syntax", as specified by > Section 3.5 of [RFC1034] and as modified by Section 2.1 of > [RFC1123]. Note that while uppercase and lowercase letters are > allowed in domain names, no significance is attached to the case. > ... And the [RFC 1034, section 3.5](https://tools.ietf.org/html/rfc1034#section-3.5): > ... > Note that while upper and lower case letters are allowed in domain > names, no significance is attached to the case. That is, two names with > the same spelling but different case are to be treated as if identical. > ... We should return the same TLS certificate for both `example.com` and `EXAMPLE.COM`. But the `autocert.Manager.GetCertificate` treats the two as totally different, it signs and returns two different TLS certificates. Moreover, now the `autocert.Manager.GetCertificate` and the `autocert.HostWhitelist` can only handle Punycode IDN. If the client sends a Unicode IDN to `autocert.Manager.GetCertificate` (cURL is doing this), the "Invalid character in DNS name" error will be triggered. This PR corrects these problems by converting the host of the TLS certificate to be obtained to Punycode via `idna.Lookup.ToASCII`. Change-Id: I993821b3a6ae532a53772e2db00524479ef111af GitHub-Last-Rev: 6c12694 GitHub-Pull-Request: #85 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/171997 Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent af44ce2 commit f856061

File tree

4 files changed

+55
-6
lines changed

4 files changed

+55
-6
lines changed

Diff for: acme/autocert/autocert.go

+19-2
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
@@ -62,10 +63,16 @@ type HostPolicy func(ctx context.Context, host string) error
6263
// HostWhitelist returns a policy where only the specified host names are allowed.
6364
// Only exact matches are currently supported. Subdomains, regexp or wildcard
6465
// will not match.
66+
//
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+
// Invalid hosts will be silently ignored.
6570
func HostWhitelist(hosts ...string) HostPolicy {
6671
whitelist := make(map[string]bool, len(hosts))
6772
for _, h := range hosts {
68-
whitelist[h] = true
73+
if h, err := idna.Lookup.ToASCII(h); err == nil {
74+
whitelist[h] = true
75+
}
6976
}
7077
return func(_ context.Context, host string) error {
7178
if !whitelist[host] {
@@ -243,7 +250,17 @@ func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
243250
if !strings.Contains(strings.Trim(name, "."), ".") {
244251
return nil, errors.New("acme/autocert: server name component count invalid")
245252
}
246-
if strings.ContainsAny(name, `+/\`) {
253+
254+
// Note that this conversion is necessary because some server names in the handshakes
255+
// started by some clients (such as cURL) are not converted to Punycode, which will
256+
// prevent us from obtaining certificates for them. In addition, we should also treat
257+
// example.com and EXAMPLE.COM as equivalent and return the same certificate for them.
258+
// Fortunately, this conversion also helped us deal with this kind of mixedcase problems.
259+
//
260+
// Due to the "σςΣ" problem (see https://unicode.org/faq/idn.html#22), we can't use
261+
// idna.Punycode.ToASCII (or just idna.ToASCII) here.
262+
name, err := idna.Lookup.ToASCII(name)
263+
if err != nil {
247264
return nil, errors.New("acme/autocert: server name contains invalid character")
248265
}
249266

Diff for: acme/autocert/autocert_test.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,28 @@ 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+
223+
func TestGetCertificate_mixedcase(t *testing.T) {
224+
man := &Manager{Prompt: AcceptTOS}
225+
defer man.stopRenew()
226+
227+
hello := clientHelloInfo("example.org", true)
228+
testGetCertificate(t, man, "example.org", hello)
229+
230+
hello = clientHelloInfo("EXAMPLE.ORG", true)
231+
testGetCertificate(t, man, "example.org", hello)
232+
}
233+
212234
func TestGetCertificate_ForceRSA(t *testing.T) {
213235
man := &Manager{
214236
Prompt: AcceptTOS,
@@ -906,13 +928,14 @@ func TestCache(t *testing.T) {
906928
}
907929

908930
func TestHostWhitelist(t *testing.T) {
909-
policy := HostWhitelist("example.com", "example.org", "*.example.net")
931+
policy := HostWhitelist("example.com", "EXAMPLE.ORG", "*.example.net", "σςΣ.com")
910932
tt := []struct {
911933
host string
912934
allow bool
913935
}{
914936
{"example.com", true},
915937
{"example.org", true},
938+
{"xn--4xaaa.com", true},
916939
{"one.example.com", false},
917940
{"two.example.org", false},
918941
{"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)