diff options
author | Roland Shoemaker <rolandshoemaker@gmail.com> | 2020-05-23 10:15:46 -0700 |
---|---|---|
committer | Roland Shoemaker <roland@golang.org> | 2020-09-30 17:51:05 +0000 |
commit | 1eeaff75f9e02c65d29d9910c1884c6c0ecc1430 (patch) | |
tree | fe5c66425e613d2c8211ea8b81cdb8063c84c6ad /src/crypto | |
parent | ae329abec0f78743ab2fbf30ef5b488376fe3c85 (diff) | |
download | go-1eeaff75f9e02c65d29d9910c1884c6c0ecc1430.tar.gz go-1eeaff75f9e02c65d29d9910c1884c6c0ecc1430.zip |
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>
Diffstat (limited to 'src/crypto')
-rw-r--r-- | src/crypto/x509/x509.go | 39 | ||||
-rw-r--r-- | src/crypto/x509/x509_test.go | 90 |
2 files changed, 121 insertions, 8 deletions
diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index 5fd4f6fa17..93dca03840 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -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) } } diff --git a/src/crypto/x509/x509_test.go b/src/crypto/x509/x509_test.go index 6345c3f5ab..e87294bde5 100644 --- a/src/crypto/x509/x509_test.go +++ b/src/crypto/x509/x509_test.go @@ -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) + } + } +} |