mirror of
https://github.com/golang/go
synced 2024-11-26 17:46:57 -07:00
crypto/x509: enforce SAN IA5String encoding restrictions
Extends the IA5String encoding restrictions that are currently applied to name constraints to dNSName, rfc822Name, and uniformResourceIdentifier elements of the SAN. The utility function isIA5String is updated to use unicode.MaxASCII rather than utf8.RuneSelf as it is somewhat more readable. Certificates that include these badly encoded names do exist, but are exceedingly rare. zlint and other linters enforce this encoding and searching censys.io reveals only three currently trusted certificates with this particular encoding issue. Fixes #26362 Change-Id: I7a4f3e165a1754e5b4bfaeabc03e01eb7367f3c9 Reviewed-on: https://go-review.googlesource.com/c/go/+/235078 Run-TryBot: Roland Shoemaker <roland@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Trust: Roland Shoemaker <roland@golang.org> Reviewed-by: Filippo Valsorda <filippo@golang.org>
This commit is contained in:
parent
ae329abec0
commit
1eeaff75f9
@ -174,6 +174,16 @@ Do not send CLs removing the interior tags from such phrases.
|
||||
by the <code>Error</code> method with <code>"tls: use of closed connection"</code>.
|
||||
</p>
|
||||
|
||||
<h3 id="crypto/x509"><a href="/pkg/crypto/x509">crypto/x509</a></h3>
|
||||
|
||||
<p><!-- CL 235078 -->
|
||||
<a href="/pkg/crypto/x509/#ParseCertificate">ParseCertificate</a> and
|
||||
<a href="/pkg/crypto/x509/#CreateCertificate">CreateCertificate</a> both
|
||||
now enforce string encoding restrictions for the fields <code>DNSNames</code>,
|
||||
<code>EmailAddresses</code>, and <code>URIs</code>. These fields can only
|
||||
contain strings with characters within the ASCII range.
|
||||
</p>
|
||||
|
||||
<h3 id="net"><a href="/pkg/net/">net</a></h3>
|
||||
|
||||
<p><!-- CL 250357 -->
|
||||
|
@ -28,7 +28,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
|
||||
@ -1085,17 +1085,29 @@ func parseSANExtension(value []byte) (dnsNames, emailAddresses []string, ipAddre
|
||||
err = forEachSAN(value, func(tag int, data []byte) error {
|
||||
switch tag {
|
||||
case nameTypeEmail:
|
||||
emailAddresses = append(emailAddresses, string(data))
|
||||
email := string(data)
|
||||
if err := isIA5String(email); err != nil {
|
||||
return errors.New("x509: SAN rfc822Name is malformed")
|
||||
}
|
||||
emailAddresses = append(emailAddresses, email)
|
||||
case nameTypeDNS:
|
||||
dnsNames = append(dnsNames, string(data))
|
||||
name := string(data)
|
||||
if err := isIA5String(name); err != nil {
|
||||
return errors.New("x509: SAN dNSName is malformed")
|
||||
}
|
||||
dnsNames = append(dnsNames, string(name))
|
||||
case nameTypeURI:
|
||||
uri, err := url.Parse(string(data))
|
||||
uriStr := string(data)
|
||||
if err := isIA5String(uriStr); err != nil {
|
||||
return errors.New("x509: SAN uniformResourceIdentifier is malformed")
|
||||
}
|
||||
uri, err := url.Parse(uriStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("x509: cannot parse URI %q: %s", string(data), err)
|
||||
return fmt.Errorf("x509: cannot parse URI %q: %s", uriStr, err)
|
||||
}
|
||||
if len(uri.Host) > 0 {
|
||||
if _, ok := domainToReverseLabels(uri.Host); !ok {
|
||||
return fmt.Errorf("x509: cannot parse URI %q: invalid domain", string(data))
|
||||
return fmt.Errorf("x509: cannot parse URI %q: invalid domain", uriStr)
|
||||
}
|
||||
}
|
||||
uris = append(uris, uri)
|
||||
@ -1625,9 +1637,15 @@ func oidInExtensions(oid asn1.ObjectIdentifier, extensions []pkix.Extension) boo
|
||||
func marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL) (derBytes []byte, err error) {
|
||||
var rawValues []asn1.RawValue
|
||||
for _, name := range dnsNames {
|
||||
if err := isIA5String(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeDNS, Class: 2, Bytes: []byte(name)})
|
||||
}
|
||||
for _, email := range emailAddresses {
|
||||
if err := isIA5String(email); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeEmail, Class: 2, Bytes: []byte(email)})
|
||||
}
|
||||
for _, rawIP := range ipAddresses {
|
||||
@ -1639,14 +1657,19 @@ func marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP, uris [
|
||||
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeIP, Class: 2, Bytes: ip})
|
||||
}
|
||||
for _, uri := range uris {
|
||||
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeURI, Class: 2, Bytes: []byte(uri.String())})
|
||||
uriStr := uri.String()
|
||||
if err := isIA5String(uriStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeURI, Class: 2, Bytes: []byte(uriStr)})
|
||||
}
|
||||
return asn1.Marshal(rawValues)
|
||||
}
|
||||
|
||||
func isIA5String(s string) error {
|
||||
for _, r := range s {
|
||||
if r >= utf8.RuneSelf {
|
||||
// Per RFC5280 "IA5String is limited to the set of ASCII characters"
|
||||
if r > unicode.MaxASCII {
|
||||
return fmt.Errorf("x509: %q cannot be encoded as an IA5String", s)
|
||||
}
|
||||
}
|
||||
|
@ -2773,3 +2773,93 @@ func TestUnknownExtKey(t *testing.T) {
|
||||
t.Errorf("expected error containing %q, got %s", errorContains, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIA5SANEnforcement(t *testing.T) {
|
||||
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("ecdsa.GenerateKey failed: %s", err)
|
||||
}
|
||||
|
||||
testURL, err := url.Parse("https://example.com/")
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse failed: %s", err)
|
||||
}
|
||||
testURL.RawQuery = "∞"
|
||||
|
||||
marshalTests := []struct {
|
||||
name string
|
||||
template *Certificate
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "marshal: unicode dNSName",
|
||||
template: &Certificate{
|
||||
SerialNumber: big.NewInt(0),
|
||||
DNSNames: []string{"∞"},
|
||||
},
|
||||
expectedError: "x509: \"∞\" cannot be encoded as an IA5String",
|
||||
},
|
||||
{
|
||||
name: "marshal: unicode rfc822Name",
|
||||
template: &Certificate{
|
||||
SerialNumber: big.NewInt(0),
|
||||
EmailAddresses: []string{"∞"},
|
||||
},
|
||||
expectedError: "x509: \"∞\" cannot be encoded as an IA5String",
|
||||
},
|
||||
{
|
||||
name: "marshal: unicode uniformResourceIdentifier",
|
||||
template: &Certificate{
|
||||
SerialNumber: big.NewInt(0),
|
||||
URIs: []*url.URL{testURL},
|
||||
},
|
||||
expectedError: "x509: \"https://example.com/?∞\" cannot be encoded as an IA5String",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range marshalTests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := CreateCertificate(rand.Reader, tc.template, tc.template, k.Public(), k)
|
||||
if err == nil {
|
||||
t.Errorf("expected CreateCertificate to fail with template: %v", tc.template)
|
||||
} else if err.Error() != tc.expectedError {
|
||||
t.Errorf("unexpected error: got %q, want %q", err.Error(), tc.expectedError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
unmarshalTests := []struct {
|
||||
name string
|
||||
cert string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "unmarshal: unicode dNSName",
|
||||
cert: "308201083081aea003020102020100300a06082a8648ce3d04030230003022180f30303031303130313030303030305a180f30303031303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d0301070342000424bcc48180d8d9db794028f2575ebe3cac79f04d7b0d0151c5292e588aac3668c495f108c626168462e0668c9705e08a211dd103a659d2684e0adf8c2bfd47baa315301330110603551d110101ff040730058203e2889e300a06082a8648ce3d04030203490030460221008ac7827ac326a6ee0fa70b2afe99af575ec60b975f820f3c25f60fff43fbccd0022100bffeed93556722d43d13e461d5b3e33efc61f6349300327d3a0196cb6da501c2",
|
||||
expectedError: "x509: SAN dNSName is malformed",
|
||||
},
|
||||
{
|
||||
name: "unmarshal: unicode rfc822Name",
|
||||
cert: "308201083081aea003020102020100300a06082a8648ce3d04030230003022180f30303031303130313030303030305a180f30303031303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d0301070342000405cb4c4ba72aac980f7b11b0285191425e29e196ce7c5df1c83f56886566e517f196657cc1b73de89ab84ce503fd634e2f2af88fde24c63ca536dc3a5eed2665a315301330110603551d110101ff040730058103e2889e300a06082a8648ce3d0403020349003046022100ed1431cd4b9bb03d88d1511a0ec128a51204375764c716280dc36e2a60142c8902210088c96d25cfaf97eea851ff17d87bb6fe619d6546656e1739f35c3566051c3d0f",
|
||||
expectedError: "x509: SAN rfc822Name is malformed",
|
||||
},
|
||||
{
|
||||
name: "unmarshal: unicode uniformResourceIdentifier",
|
||||
cert: "3082011b3081c3a003020102020100300a06082a8648ce3d04030230003022180f30303031303130313030303030305a180f30303031303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d03010703420004ce0a79b511701d9188e1ea76bcc5907f1db51de6cc1a037b803f256e8588145ca409d120288bfeb4e38f3088104674d374b35bb91fc80d768d1d519dbe2b0b5aa32a302830260603551d110101ff041c301a861868747470733a2f2f6578616d706c652e636f6d2f3fe2889e300a06082a8648ce3d0403020347003044022044f4697779fd1dae1e382d2452413c5c5ca67851e267d6bc64a8d164977c172c0220505015e657637aa1945d46e7650b6f59b968fc1508ca8b152c99f782446dfc81",
|
||||
expectedError: "x509: SAN uniformResourceIdentifier is malformed",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range unmarshalTests {
|
||||
der, err := hex.DecodeString(tc.cert)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode test cert: %s", err)
|
||||
}
|
||||
_, err = ParseCertificate(der)
|
||||
if err == nil {
|
||||
t.Error("expected CreateCertificate to fail")
|
||||
} else if err.Error() != tc.expectedError {
|
||||
t.Errorf("unexpected error: got %q, want %q", err.Error(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user