diff --git a/VERSION b/VERSION index e54793d74a8ff1..9e2157943c8db8 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -go1.25.4 -time 2025-10-31T13:24:27Z +go1.25.5 +time 2025-11-26T02:01:51Z diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go index a5851845164d10..bc91b28401fce5 100644 --- a/src/crypto/x509/name_constraints_test.go +++ b/src/crypto/x509/name_constraints_test.go @@ -1624,6 +1624,40 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "URI with IP", }, + // #87: subdomain excluded constraints preclude wildcard names + { + roots: []constraintsSpec{ + { + bad: []string{"dns:foo.example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.example.com"}, + }, + expectedError: "\"*.example.com\" is excluded by constraint \"foo.example.com\"", + }, + // #88: wildcard names are not matched by subdomain permitted constraints + { + roots: []constraintsSpec{ + { + ok: []string{"dns:foo.example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.example.com"}, + }, + expectedError: "\"*.example.com\" is not permitted", + }, } func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) { diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go index bf7e7ec058db2b..3de9f93b2c4b16 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -110,31 +110,38 @@ type HostnameError struct { func (h HostnameError) Error() string { c := h.Certificate + maxNamesIncluded := 100 if !c.hasSANExtension() && matchHostnames(c.Subject.CommonName, h.Host) { return "x509: certificate relies on legacy Common Name field, use SANs instead" } - var valid string + var valid strings.Builder if ip := net.ParseIP(h.Host); ip != nil { // Trying to validate an IP if len(c.IPAddresses) == 0 { return "x509: cannot validate certificate for " + h.Host + " because it doesn't contain any IP SANs" } + if len(c.IPAddresses) >= maxNamesIncluded { + return fmt.Sprintf("x509: certificate is valid for %d IP SANs, but none matched %s", len(c.IPAddresses), h.Host) + } for _, san := range c.IPAddresses { - if len(valid) > 0 { - valid += ", " + if valid.Len() > 0 { + valid.WriteString(", ") } - valid += san.String() + valid.WriteString(san.String()) } } else { - valid = strings.Join(c.DNSNames, ", ") + if len(c.DNSNames) >= maxNamesIncluded { + return fmt.Sprintf("x509: certificate is valid for %d names, but none matched %s", len(c.DNSNames), h.Host) + } + valid.WriteString(strings.Join(c.DNSNames, ", ")) } - if len(valid) == 0 { + if valid.Len() == 0 { return "x509: certificate is not valid for any names, but wanted to match " + h.Host } - return "x509: certificate is valid for " + valid + ", not " + h.Host + return "x509: certificate is valid for " + valid.String() + ", not " + h.Host } // UnknownAuthorityError results when the certificate issuer is unknown @@ -429,7 +436,7 @@ func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { return reverseLabels, true } -func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { +func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, excluded bool, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { // If the constraint contains an @, then it specifies an exact mailbox // name. if strings.Contains(constraint, "@") { @@ -442,10 +449,10 @@ func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, reversedDom // Otherwise the constraint is like a DNS constraint of the domain part // of the mailbox. - return matchDomainConstraint(mailbox.domain, constraint, reversedDomainsCache, reversedConstraintsCache) + return matchDomainConstraint(mailbox.domain, constraint, excluded, reversedDomainsCache, reversedConstraintsCache) } -func matchURIConstraint(uri *url.URL, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { +func matchURIConstraint(uri *url.URL, constraint string, excluded bool, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { // From RFC 5280, Section 4.2.1.10: // “a uniformResourceIdentifier that does not include an authority // component with a host name specified as a fully qualified domain @@ -474,7 +481,7 @@ func matchURIConstraint(uri *url.URL, constraint string, reversedDomainsCache ma return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String()) } - return matchDomainConstraint(host, constraint, reversedDomainsCache, reversedConstraintsCache) + return matchDomainConstraint(host, constraint, excluded, reversedDomainsCache, reversedConstraintsCache) } func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { @@ -491,7 +498,7 @@ func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { return true, nil } -func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { +func matchDomainConstraint(domain, constraint string, excluded bool, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { // The meaning of zero length constraints is not specified, but this // code follows NSS and accepts them as matching everything. if len(constraint) == 0 { @@ -508,6 +515,11 @@ func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[s reversedDomainsCache[domain] = domainLabels } + wildcardDomain := false + if len(domain) > 0 && domain[0] == '*' { + wildcardDomain = true + } + // RFC 5280 says that a leading period in a domain name means that at // least one label must be prepended, but only for URI and email // constraints, not DNS constraints. The code also supports that @@ -534,6 +546,11 @@ func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[s return false, nil } + if excluded && wildcardDomain && len(domainLabels) > 1 && len(constraintLabels) > 0 { + domainLabels = domainLabels[:len(domainLabels)-1] + constraintLabels = constraintLabels[:len(constraintLabels)-1] + } + for i, constraintLabel := range constraintLabels { if !strings.EqualFold(constraintLabel, domainLabels[i]) { return false, nil @@ -553,7 +570,7 @@ func (c *Certificate) checkNameConstraints(count *int, nameType string, name string, parsedName any, - match func(parsedName, constraint any) (match bool, err error), + match func(parsedName, constraint any, excluded bool) (match bool, err error), permitted, excluded any) error { excludedValue := reflect.ValueOf(excluded) @@ -565,7 +582,7 @@ func (c *Certificate) checkNameConstraints(count *int, for i := 0; i < excludedValue.Len(); i++ { constraint := excludedValue.Index(i).Interface() - match, err := match(parsedName, constraint) + match, err := match(parsedName, constraint, true) if err != nil { return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} } @@ -587,7 +604,7 @@ func (c *Certificate) checkNameConstraints(count *int, constraint := permittedValue.Index(i).Interface() var err error - if ok, err = match(parsedName, constraint); err != nil { + if ok, err = match(parsedName, constraint, false); err != nil { return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} } @@ -679,8 +696,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox, - func(parsedName, constraint any) (bool, error) { - return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), reversedDomainsCache, reversedConstraintsCache) + func(parsedName, constraint any, excluded bool) (bool, error) { + return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache) }, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil { return err } @@ -692,8 +709,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name, - func(parsedName, constraint any) (bool, error) { - return matchDomainConstraint(parsedName.(string), constraint.(string), reversedDomainsCache, reversedConstraintsCache) + func(parsedName, constraint any, excluded bool) (bool, error) { + return matchDomainConstraint(parsedName.(string), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache) }, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil { return err } @@ -706,8 +723,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri, - func(parsedName, constraint any) (bool, error) { - return matchURIConstraint(parsedName.(*url.URL), constraint.(string), reversedDomainsCache, reversedConstraintsCache) + func(parsedName, constraint any, excluded bool) (bool, error) { + return matchURIConstraint(parsedName.(*url.URL), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache) }, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil { return err } @@ -719,7 +736,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V } if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip, - func(parsedName, constraint any) (bool, error) { + func(parsedName, constraint any, _ bool) (bool, error) { return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet)) }, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil { return err diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go index 60a4cea9146adf..9a21218ee4b465 100644 --- a/src/crypto/x509/verify_test.go +++ b/src/crypto/x509/verify_test.go @@ -10,13 +10,16 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "crypto/rsa" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "errors" "fmt" "internal/testenv" + "log" "math/big" + "net" "os" "os/exec" "runtime" @@ -89,6 +92,26 @@ var verifyTests = []verifyTest{ errorCallback: expectHostnameError("certificate is valid for"), }, + { + name: "TooManyDNS", + leaf: generatePEMCertWithRepeatSAN(1677615892, 200, "fake.dns"), + roots: []string{generatePEMCertWithRepeatSAN(1677615892, 200, "fake.dns")}, + currentTime: 1677615892, + dnsName: "www.example.com", + systemSkip: true, // does not chain to a system root + + errorCallback: expectHostnameError("certificate is valid for 200 names, but none matched"), + }, + { + name: "TooManyIPs", + leaf: generatePEMCertWithRepeatSAN(1677615892, 150, "4.3.2.1"), + roots: []string{generatePEMCertWithRepeatSAN(1677615892, 150, "4.3.2.1")}, + currentTime: 1677615892, + dnsName: "1.2.3.4", + systemSkip: true, // does not chain to a system root + + errorCallback: expectHostnameError("certificate is valid for 150 IP SANs, but none matched"), + }, { name: "IPMissing", leaf: googleLeaf, @@ -552,6 +575,30 @@ func nameToKey(name *pkix.Name) string { return strings.Join(name.Country, ",") + "/" + strings.Join(name.Organization, ",") + "/" + strings.Join(name.OrganizationalUnit, ",") + "/" + name.CommonName } +func generatePEMCertWithRepeatSAN(currentTime int64, count int, san string) string { + cert := Certificate{ + NotBefore: time.Unix(currentTime, 0), + NotAfter: time.Unix(currentTime, 0), + } + if ip := net.ParseIP(san); ip != nil { + cert.IPAddresses = slices.Repeat([]net.IP{ip}, count) + } else { + cert.DNSNames = slices.Repeat([]string{san}, count) + } + privKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + log.Fatal(err) + } + certBytes, err := CreateCertificate(rand.Reader, &cert, &cert, &privKey.PublicKey, privKey) + if err != nil { + log.Fatal(err) + } + return string(pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + })) +} + const gtsIntermediate = `-----BEGIN CERTIFICATE----- MIIFljCCA36gAwIBAgINAgO8U1lrNMcY9QFQZjANBgkqhkiG9w0BAQsFADBHMQsw CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU @@ -1352,7 +1399,7 @@ var nameConstraintTests = []struct { func TestNameConstraints(t *testing.T) { for i, test := range nameConstraintTests { - result, err := matchDomainConstraint(test.domain, test.constraint, map[string][]string{}, map[string][]string{}) + result, err := matchDomainConstraint(test.domain, test.constraint, false, map[string][]string{}, map[string][]string{}) if err != nil && !test.expectError { t.Errorf("unexpected error for test #%d: domain=%s, constraint=%s, err=%s", i, test.domain, test.constraint, err) diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go index c34cc795a0ea90..968dbaf061e4e7 100644 --- a/src/internal/syscall/windows/syscall_windows.go +++ b/src/internal/syscall/windows/syscall_windows.go @@ -529,7 +529,7 @@ const ( //sys GetOverlappedResult(handle syscall.Handle, overlapped *syscall.Overlapped, done *uint32, wait bool) (err error) //sys CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW -//sys ReOpenFile(filehandle syscall.Handle, desiredAccess uint32, shareMode uint32, flagAndAttributes uint32) (handle syscall.Handle, err error) +//sys ReOpenFile(filehandle syscall.Handle, desiredAccess uint32, shareMode uint32, flagAndAttributes uint32) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] // NTStatus corresponds with NTSTATUS, error values returned by ntdll.dll and // other native functions. diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go index b3f01ef5c00281..ba6c017ea96458 100644 --- a/src/internal/syscall/windows/zsyscall_windows.go +++ b/src/internal/syscall/windows/zsyscall_windows.go @@ -435,7 +435,7 @@ func MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, func ReOpenFile(filehandle syscall.Handle, desiredAccess uint32, shareMode uint32, flagAndAttributes uint32) (handle syscall.Handle, err error) { r0, _, e1 := syscall.Syscall6(procReOpenFile.Addr(), 4, uintptr(filehandle), uintptr(desiredAccess), uintptr(shareMode), uintptr(flagAndAttributes), 0, 0) handle = syscall.Handle(r0) - if handle == 0 { + if handle == syscall.InvalidHandle { err = errnoErr(e1) } return diff --git a/src/mime/grammar.go b/src/mime/grammar.go index cc578fbcfd4168..1efd8a16dec607 100644 --- a/src/mime/grammar.go +++ b/src/mime/grammar.go @@ -62,7 +62,9 @@ func isTokenChar(c byte) bool { 1<<'^' | 1<<'_' | 1<<'`' | + 1<<'{' | 1<<'|' | + 1<<'}' | 1<<'~' return ((uint64(1)<>64)) != 0 diff --git a/src/mime/mediatype_test.go b/src/mime/mediatype_test.go index 251df8d6691ab9..da8d64de7a3f0c 100644 --- a/src/mime/mediatype_test.go +++ b/src/mime/mediatype_test.go @@ -413,6 +413,9 @@ func init() { // Issue #48866: duplicate parameters containing equal values should be allowed {`text; charset=utf-8; charset=utf-8; format=fixed`, "text", m("charset", "utf-8", "format", "fixed")}, {`text; charset=utf-8; format=flowed; charset=utf-8`, "text", m("charset", "utf-8", "format", "flowed")}, + + // Issue #76236: '{' and '}' are token chars. + {"attachment; filename={file}.png", "attachment", m("filename", "{file}.png")}, } }