Compare commits

...

6 Commits

Author SHA1 Message Date
Roland Shoemaker
cb75daf3b2 [release-branch.go1.24] crypto/tls: check verifiedChains roots when resuming sessions
When resuming TLS sessions, on the server and client verify that the
chains stored in the session state (verifiedChains) are still acceptable
with regards to the Config by checking for the inclusion of the root in
either ClientCAs (server) or RootCAs (client). This prevents resuming
a session with a certificate chain that would be rejected during a full
handshake due to an untrusted root.

Updates #77113
Updates #77355
Updates CVE-2025-68121

Change-Id: I11fe00909ef1961c24ecf80bf5b97f7b1121d359
Reviewed-on: https://go-review.googlesource.com/c/go/+/737700
Auto-Submit: Roland Shoemaker <roland@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Coia Prant <coiaprant@gmail.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-on: https://go-review.googlesource.com/c/go/+/740062
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <nsh@golang.org>
Reviewed-by: Nicholas Husin <husin@google.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
2026-01-28 14:10:33 -08:00
Roland Shoemaker
5f07b226f9 [release-branch.go1.24] crypto/tls: add verifiedChains expiration checking during resumption
When resuming a session, check that the verifiedChains contain at least
one chain that is still valid at the time of resumption. If not, trigger
a new handshake.

Updates #77113
Updates #77355
Updates CVE-2025-68121

Change-Id: I14f585c43da17802513cbdd5b10c552d7a38b34e
Reviewed-on: https://go-review.googlesource.com/c/go/+/739321
Reviewed-by: Coia Prant <coiaprant@gmail.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/740061
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-by: Nicholas Husin <nsh@golang.org>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
2026-01-28 14:03:19 -08:00
Roland Shoemaker
b2abaab3fc [release-branch.go1.24] Revert "crypto/tls: don't copy auto-rotated session ticket keys in Config.Clone"
This reverts CL 736709 (commit bba24719a4).

Updates #77113
Updates #77355
Updates CVE-2025-68121

Change-Id: I0261cb75e9adf9d0ac9890dc91ae8476b8988ba0
Reviewed-on: https://go-review.googlesource.com/c/go/+/739320
Reviewed-by: Coia Prant <coiaprant@gmail.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/740060
Reviewed-by: Damien Neil <dneil@google.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Nicholas Husin <nsh@golang.org>
Reviewed-by: Nicholas Husin <husin@google.com>
2026-01-28 14:03:15 -08:00
Roland Shoemaker
73fe85f0ea [release-branch.go1.24] cmd/go: update VCS commands to use safer flag/argument syntax
In various situations, the toolchain invokes VCS commands. Some of these
commands take arbitrary input, either provided by users or fetched from
external sources. To prevent potential command injection vulnerabilities
or misinterpretation of arguments as flags, this change updates the VCS
commands to use various techniques to separate flags from positional
arguments, and to directly associate flags with their values.

Additionally, we update the environment variable for Mercurial to use
`HGPLAIN=+strictflags`, which is the more explicit way to disable user
configurations (intended or otherwise) that might interfere with command
execution.

We also now disallow version strings from being prefixed with '-' or
'/', as doing so opens us up to making the same mistake again in the
future. As far as we know there are currently ~0 public modules affected
by this.

While I was working on cmd/go/internal/vcs, I also noticed that a
significant portion of the commands being implemented were dead code.
In order to reduce the maintenance burden and surface area for potential
issues, I removed the dead code for unused commands.

We should probably follow up with a more structured change to make it
harder to accidentally re-introduce these issues in the future, but for
now this addresses the issue at hand.

Thanks to splitline (@splitline) from DEVCORE Research Team for
reporting this issue.

Fixes CVE-2025-68119
Updates #77099
Fixes #77103

Change-Id: I9d9f4ee05b95be49fe14edf71a1b8e6c0784378e
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3260
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/736710
Auto-Submit: Michael Pratt <mpratt@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
(cherry picked from commit 94a1296a45)
Reviewed-on: https://go-review.googlesource.com/c/go/+/739421
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
2026-01-28 13:58:41 -08:00
Roland Shoemaker
2c4733c609 [release-branch.go1.24] crypto/x509: fix single label excluded name constraints handling
Only strip labels when both the domain and constraint have more than one
label.

Fixes #76935
Fixes #77322

Change-Id: I1144c9f03cbfc3b858af153a839b193bb934618d
Reviewed-on: https://go-review.googlesource.com/c/go/+/739420
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
2026-01-28 13:37:59 -08:00
Neal Patel
14d0bb39c1 [release-branch.go1.24] cmd/go: remove user-content from doc strings in cgo ASTs.
Thank you to RyotaK (https://ryotak.net) of GMO Flatt Security Inc. for reporting this issue.

Updates #76697
Fixes #77128
Fixes CVE-2025-61732

Change-Id: Ie2a96b79a813e362cbf8e6cb0e3c2d0c022bcb29
Reviewed-on: https://go-review.googlesource.com/c/go/+/740001
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
2026-01-28 12:29:22 -08:00
18 changed files with 428 additions and 407 deletions

View File

@@ -301,17 +301,12 @@ func (f *File) saveExport(x interface{}, context astContext) {
error_(c.Pos(), "export comment has wrong name %q, want %q", name, n.Name.Name)
}
doc := ""
for _, c1 := range n.Doc.List {
if c1 != c {
doc += c1.Text + "\n"
}
}
f.ExpFunc = append(f.ExpFunc, &ExpFunc{
Func: n,
ExpName: name,
Doc: doc,
// Caution: Do not set the Doc field on purpose
// to ensure that there are no unintended artifacts
// in the binary. See https://go.dev/issue/76697.
})
break
}

View File

@@ -311,7 +311,10 @@ func runEdit(ctx context.Context, cmd *base.Command, args []string) {
// parsePathVersion parses -flag=arg expecting arg to be path@version.
func parsePathVersion(flag, arg string) (path, version string) {
before, after, found := strings.Cut(arg, "@")
before, after, found, err := modload.ParsePathVersion(arg)
if err != nil {
base.Fatalf("go: -%s=%s: %v", flag, arg, err)
}
if !found {
base.Fatalf("go: -%s=%s: need path@version", flag, arg)
}
@@ -345,7 +348,10 @@ func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version
if allowDirPath && modfile.IsDirectoryPath(arg) {
return arg, "", nil
}
before, after, found := strings.Cut(arg, "@")
before, after, found, err := modload.ParsePathVersion(arg)
if err != nil {
return "", "", err
}
if !found {
path = arg
} else {

View File

@@ -230,7 +230,7 @@ func (r *gitRepo) loadRefs(ctx context.Context) (map[string]string, error) {
r.refsErr = err
return
}
out, gitErr := r.runGit(ctx, "git", "ls-remote", "-q", r.remote)
out, gitErr := r.runGit(ctx, "git", "ls-remote", "-q", "--end-of-options", r.remote)
release()
if gitErr != nil {
@@ -489,7 +489,7 @@ func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err erro
if fromTag && !slices.Contains(info.Tags, tag) {
// The local repo includes the commit hash we want, but it is missing
// the corresponding tag. Add that tag and try again.
_, err := r.runGit(ctx, "git", "tag", tag, hash)
_, err := r.runGit(ctx, "git", "tag", "--end-of-options", tag, hash)
if err != nil {
return nil, err
}
@@ -538,7 +538,7 @@ func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err erro
// an apparent Git bug introduced in Git 2.21 (commit 61c771),
// which causes the handler for protocol version 1 to sometimes miss
// tags that point to the requested commit (see https://go.dev/issue/56881).
_, err = r.runGit(ctx, "git", "-c", "protocol.version=2", "fetch", "-f", "--depth=1", r.remote, refspec)
_, err = r.runGit(ctx, "git", "-c", "protocol.version=2", "fetch", "-f", "--depth=1", "--end-of-options", r.remote, refspec)
release()
if err == nil {
@@ -584,12 +584,12 @@ func (r *gitRepo) fetchRefsLocked(ctx context.Context) error {
}
defer release()
if _, err := r.runGit(ctx, "git", "fetch", "-f", r.remote, "refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"); err != nil {
if _, err := r.runGit(ctx, "git", "fetch", "-f", "--end-of-options", r.remote, "refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"); err != nil {
return err
}
if _, err := os.Stat(filepath.Join(r.dir, "shallow")); err == nil {
if _, err := r.runGit(ctx, "git", "fetch", "--unshallow", "-f", r.remote); err != nil {
if _, err := r.runGit(ctx, "git", "fetch", "--unshallow", "-f", "--end-of-options", r.remote); err != nil {
return err
}
}
@@ -602,7 +602,7 @@ func (r *gitRepo) fetchRefsLocked(ctx context.Context) error {
// statLocal returns a new RevInfo describing rev in the local git repository.
// It uses version as info.Version.
func (r *gitRepo) statLocal(ctx context.Context, version, rev string) (*RevInfo, error) {
out, err := r.runGit(ctx, "git", "-c", "log.showsignature=false", "log", "--no-decorate", "-n1", "--format=format:%H %ct %D", rev, "--")
out, err := r.runGit(ctx, "git", "-c", "log.showsignature=false", "log", "--no-decorate", "-n1", "--format=format:%H %ct %D", "--end-of-options", rev, "--")
if err != nil {
// Return info with Origin.RepoSum if possible to allow caching of negative lookup.
var info *RevInfo
@@ -692,7 +692,7 @@ func (r *gitRepo) ReadFile(ctx context.Context, rev, file string, maxSize int64)
if err != nil {
return nil, err
}
out, err := r.runGit(ctx, "git", "cat-file", "blob", info.Name+":"+file)
out, err := r.runGit(ctx, "git", "cat-file", "--end-of-options", "blob", info.Name+":"+file)
if err != nil {
return nil, fs.ErrNotExist
}
@@ -710,7 +710,7 @@ func (r *gitRepo) RecentTag(ctx context.Context, rev, prefix string, allowed fun
// result is definitive.
describe := func() (definitive bool) {
var out []byte
out, err = r.runGit(ctx, "git", "for-each-ref", "--format", "%(refname)", "refs/tags", "--merged", rev)
out, err = r.runGit(ctx, "git", "for-each-ref", "--format=%(refname)", "--merged="+rev)
if err != nil {
return true
}
@@ -859,7 +859,7 @@ func (r *gitRepo) ReadZip(ctx context.Context, rev, subdir string, maxSize int64
// TODO: Use maxSize or drop it.
args := []string{}
if subdir != "" {
args = append(args, "--", subdir)
args = append(args, subdir)
}
info, err := r.Stat(ctx, rev) // download rev into local git repo
if err != nil {
@@ -881,7 +881,7 @@ func (r *gitRepo) ReadZip(ctx context.Context, rev, subdir string, maxSize int64
// text file line endings. Setting -c core.autocrlf=input means only
// translate files on the way into the repo, not on the way out (archive).
// The -c core.eol=lf should be unnecessary but set it anyway.
archive, err := r.runGit(ctx, "git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", info.Name, args)
archive, err := r.runGit(ctx, "git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", "--end-of-options", info.Name, args)
if err != nil {
if bytes.Contains(err.(*RunError).Stderr, []byte("did not match any files")) {
return nil, fs.ErrNotExist

View File

@@ -176,20 +176,20 @@ var vcsCmds = map[string]*vcsCmd{
branchRE: re(`(?m)^[^\n]+$`),
badLocalRevRE: re(`(?m)^(tip)$`),
statLocal: func(rev, remote string) []string {
return []string{"hg", "log", "-l1", "-r", rev, "--template", "{node} {date|hgdate} {tags}"}
return []string{"hg", "log", "-l1", fmt.Sprintf("--rev=%s", rev), "--template", "{node} {date|hgdate} {tags}"}
},
parseStat: hgParseStat,
fetch: []string{"hg", "pull", "-f"},
latest: "tip",
readFile: func(rev, file, remote string) []string {
return []string{"hg", "cat", "-r", rev, file}
return []string{"hg", "cat", fmt.Sprintf("--rev=%s", rev), "--", file}
},
readZip: func(rev, subdir, remote, target string) []string {
pattern := []string{}
if subdir != "" {
pattern = []string{"-I", subdir + "/**"}
pattern = []string{fmt.Sprintf("--include=%s", subdir+"/**")}
}
return str.StringList("hg", "archive", "-t", "zip", "--no-decode", "-r", rev, "--prefix=prefix/", pattern, "--", target)
return str.StringList("hg", "archive", "-t", "zip", "--no-decode", fmt.Sprintf("--rev=%s", rev), "--prefix=prefix/", pattern, "--", target)
},
},
@@ -229,19 +229,19 @@ var vcsCmds = map[string]*vcsCmd{
tagRE: re(`(?m)^\S+`),
badLocalRevRE: re(`^revno:-`),
statLocal: func(rev, remote string) []string {
return []string{"bzr", "log", "-l1", "--long", "--show-ids", "-r", rev}
return []string{"bzr", "log", "-l1", "--long", "--show-ids", fmt.Sprintf("--revision=%s", rev)}
},
parseStat: bzrParseStat,
latest: "revno:-1",
readFile: func(rev, file, remote string) []string {
return []string{"bzr", "cat", "-r", rev, file}
return []string{"bzr", "cat", fmt.Sprintf("--revision=%s", rev), "--", file}
},
readZip: func(rev, subdir, remote, target string) []string {
extra := []string{}
if subdir != "" {
extra = []string{"./" + subdir}
}
return str.StringList("bzr", "export", "--format=zip", "-r", rev, "--root=prefix/", "--", target, extra)
return str.StringList("bzr", "export", "--format=zip", fmt.Sprintf("--revision=%s", rev), "--root=prefix/", "--", target, extra)
},
},
@@ -256,17 +256,17 @@ var vcsCmds = map[string]*vcsCmd{
},
tagRE: re(`XXXTODO`),
statLocal: func(rev, remote string) []string {
return []string{"fossil", "info", "-R", ".fossil", rev}
return []string{"fossil", "info", "-R", ".fossil", "--", rev}
},
parseStat: fossilParseStat,
latest: "trunk",
readFile: func(rev, file, remote string) []string {
return []string{"fossil", "cat", "-R", ".fossil", "-r", rev, file}
return []string{"fossil", "cat", "-R", ".fossil", fmt.Sprintf("-r=%s", rev), "--", file}
},
readZip: func(rev, subdir, remote, target string) []string {
extra := []string{}
if subdir != "" && !strings.ContainsAny(subdir, "*?[],") {
extra = []string{"--include", subdir}
extra = []string{fmt.Sprintf("--include=%s", subdir)}
}
// Note that vcsRepo.ReadZip below rewrites this command
// to run in a different directory, to work around a fossil bug.

View File

@@ -139,7 +139,10 @@ func errSet(err error) pathSet { return pathSet{err: err} }
// newQuery returns a new query parsed from the raw argument,
// which must be either path or path@version.
func newQuery(raw string) (*query, error) {
pattern, rawVers, found := strings.Cut(raw, "@")
pattern, rawVers, found, err := modload.ParsePathVersion(raw)
if err != nil {
return nil, err
}
if found && (strings.Contains(rawVers, "@") || rawVers == "") {
return nil, fmt.Errorf("invalid module version syntax %q", raw)
}

View File

@@ -12,7 +12,6 @@ import (
"io/fs"
"os"
"path/filepath"
"strings"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
@@ -88,7 +87,16 @@ func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
return nil
}
if path, vers, found := strings.Cut(path, "@"); found {
path, vers, found, err := ParsePathVersion(path)
if err != nil {
return &modinfo.ModulePublic{
Path: path,
Error: &modinfo.ModuleError{
Err: err.Error(),
},
}
}
if found {
m := module.Version{Path: path, Version: vers}
return moduleInfo(ctx, nil, m, 0, nil)
}

View File

@@ -150,7 +150,11 @@ func listModules(ctx context.Context, rs *Requirements, args []string, mode List
}
continue
}
if path, vers, found := strings.Cut(arg, "@"); found {
path, vers, found, err := ParsePathVersion(arg)
if err != nil {
base.Fatalf("go: %v", err)
}
if found {
if vers == "upgrade" || vers == "patch" {
if _, ok := rs.rootSelected(path); !ok || rs.pruning == unpruned {
needFullGraph = true
@@ -176,7 +180,11 @@ func listModules(ctx context.Context, rs *Requirements, args []string, mode List
matchedModule := map[module.Version]bool{}
for _, arg := range args {
if path, vers, found := strings.Cut(arg, "@"); found {
path, vers, found, err := ParsePathVersion(arg)
if err != nil {
base.Fatalf("go: %v", err)
}
if found {
var current string
if mg == nil {
current, _ = rs.rootSelected(path)
@@ -319,3 +327,21 @@ func modinfoError(path, vers string, err error) *modinfo.ModuleError {
return &modinfo.ModuleError{Err: err.Error()}
}
// ParsePathVersion parses arg expecting arg to be path@version. If there is no
// '@' in arg, found is false, vers is "", and path is arg. This mirrors the
// typical usage of strings.Cut. ParsePathVersion is meant to be a general
// replacement for strings.Cut in module version parsing. If the version is
// invalid, an error is returned. The version is considered invalid if it is
// prefixed with '-' or '/', which can cause security problems when constructing
// commands to execute that use the version.
func ParsePathVersion(arg string) (path, vers string, found bool, err error) {
path, vers, found = strings.Cut(arg, "@")
if !found {
return arg, "", false, nil
}
if len(vers) > 0 && (vers[0] == '-' || vers[0] == '/') {
return "", "", false, fmt.Errorf("invalid module version %q", vers)
}
return path, vers, true, nil
}

View File

@@ -668,7 +668,10 @@ func goInstallVersion(minVers string) bool {
if !strings.Contains(pkgArg, "@") || build.IsLocalImport(pkgArg) || filepath.IsAbs(pkgArg) {
return false
}
path, version, _ := strings.Cut(pkgArg, "@")
path, version, _, err := modload.ParsePathVersion(pkgArg)
if err != nil {
base.Fatalf("go: %v", err)
}
if path == "" || version == "" || gover.IsToolchain(path) {
return false
}
@@ -704,7 +707,7 @@ func goInstallVersion(minVers string) bool {
allowed = nil
}
noneSelected := func(path string) (version string) { return "none" }
_, err := modload.QueryPackages(ctx, path, version, noneSelected, allowed)
_, err = modload.QueryPackages(ctx, path, version, noneSelected, allowed)
if errors.Is(err, gover.ErrTooNew) {
// Run early switch, same one go install or go run would eventually do,
// if it understood all the command-line flags.

View File

@@ -17,7 +17,6 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
@@ -41,20 +40,10 @@ type Cmd struct {
Env []string // any environment values to set/override
RootNames []rootName // filename and mode indicating the root of a checkout directory
CreateCmd []string // commands to download a fresh copy of a repository
DownloadCmd []string // commands to download updates into an existing repository
TagCmd []tagCmd // commands to list tags
TagLookupCmd []tagCmd // commands to lookup tags before running tagSyncCmd
TagSyncCmd []string // commands to sync to specific tag
TagSyncDefault []string // commands to sync to default tag
Scheme []string
PingCmd string
RemoteRepo func(v *Cmd, rootDir string) (remoteRepo string, err error)
ResolveRepo func(v *Cmd, rootDir, remoteRepo string) (realRepo string, err error)
Status func(v *Cmd, rootDir string) (Status, error)
Status func(v *Cmd, rootDir string) (Status, error)
}
// Status is the current state of a local repository.
@@ -157,40 +146,16 @@ var vcsHg = &Cmd{
Name: "Mercurial",
Cmd: "hg",
// HGPLAIN=1 turns off additional output that a user may have enabled via
// config options or certain extensions.
Env: []string{"HGPLAIN=1"},
// HGPLAIN=+strictflags turns off additional output that a user may have
// enabled via config options or certain extensions.
Env: []string{"HGPLAIN=+strictflags"},
RootNames: []rootName{
{filename: ".hg", isDir: true},
},
CreateCmd: []string{"clone -U -- {repo} {dir}"},
DownloadCmd: []string{"pull"},
// We allow both tag and branch names as 'tags'
// for selecting a version. This lets people have
// a go.release.r60 branch and a go1 branch
// and make changes in both, without constantly
// editing .hgtags.
TagCmd: []tagCmd{
{"tags", `^(\S+)`},
{"branches", `^(\S+)`},
},
TagSyncCmd: []string{"update -r {tag}"},
TagSyncDefault: []string{"update default"},
Scheme: []string{"https", "http", "ssh"},
PingCmd: "identify -- {scheme}://{repo}",
RemoteRepo: hgRemoteRepo,
Status: hgStatus,
}
func hgRemoteRepo(vcsHg *Cmd, rootDir string) (remoteRepo string, err error) {
out, err := vcsHg.runOutput(rootDir, "paths default")
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
Scheme: []string{"https", "http", "ssh"},
PingCmd: "identify -- {scheme}://{repo}",
Status: hgStatus,
}
func hgStatus(vcsHg *Cmd, rootDir string) (Status, error) {
@@ -253,25 +218,6 @@ var vcsGit = &Cmd{
{filename: ".git", isDir: true},
},
CreateCmd: []string{"clone -- {repo} {dir}", "-go-internal-cd {dir} submodule update --init --recursive"},
DownloadCmd: []string{"pull --ff-only", "submodule update --init --recursive"},
TagCmd: []tagCmd{
// tags/xxx matches a git tag named xxx
// origin/xxx matches a git branch named xxx on the default remote repository
{"show-ref", `(?:tags|origin)/(\S+)$`},
},
TagLookupCmd: []tagCmd{
{"show-ref tags/{tag} origin/{tag}", `((?:tags|origin)/\S+)$`},
},
TagSyncCmd: []string{"checkout {tag}", "submodule update --init --recursive"},
// both createCmd and downloadCmd update the working dir.
// No need to do more here. We used to 'checkout master'
// but that doesn't work if the default branch is not named master.
// DO NOT add 'checkout master' here.
// See golang.org/issue/9032.
TagSyncDefault: []string{"submodule update --init --recursive"},
Scheme: []string{"git", "https", "http", "git+ssh", "ssh"},
// Leave out the '--' separator in the ls-remote command: git 2.7.4 does not
@@ -280,54 +226,7 @@ var vcsGit = &Cmd{
// See golang.org/issue/33836.
PingCmd: "ls-remote {scheme}://{repo}",
RemoteRepo: gitRemoteRepo,
Status: gitStatus,
}
// scpSyntaxRe matches the SCP-like addresses used by Git to access
// repositories by SSH.
var scpSyntaxRe = lazyregexp.New(`^(\w+)@([\w.-]+):(.*)$`)
func gitRemoteRepo(vcsGit *Cmd, rootDir string) (remoteRepo string, err error) {
const cmd = "config remote.origin.url"
outb, err := vcsGit.run1(rootDir, cmd, nil, false)
if err != nil {
// if it doesn't output any message, it means the config argument is correct,
// but the config value itself doesn't exist
if outb != nil && len(outb) == 0 {
return "", errors.New("remote origin not found")
}
return "", err
}
out := strings.TrimSpace(string(outb))
var repoURL *urlpkg.URL
if m := scpSyntaxRe.FindStringSubmatch(out); m != nil {
// Match SCP-like syntax and convert it to a URL.
// Eg, "git@github.com:user/repo" becomes
// "ssh://git@github.com/user/repo".
repoURL = &urlpkg.URL{
Scheme: "ssh",
User: urlpkg.User(m[1]),
Host: m[2],
Path: m[3],
}
} else {
repoURL, err = urlpkg.Parse(out)
if err != nil {
return "", err
}
}
// Iterate over insecure schemes too, because this function simply
// reports the state of the repo. If we can't see insecure schemes then
// we can't report the actual repo URL.
for _, s := range vcsGit.Scheme {
if repoURL.Scheme == s {
return repoURL.String(), nil
}
}
return "", errors.New("unable to parse output of git " + cmd)
Status: gitStatus,
}
func gitStatus(vcsGit *Cmd, rootDir string) (Status, error) {
@@ -367,62 +266,9 @@ var vcsBzr = &Cmd{
{filename: ".bzr", isDir: true},
},
CreateCmd: []string{"branch -- {repo} {dir}"},
// Without --overwrite bzr will not pull tags that changed.
// Replace by --overwrite-tags after http://pad.lv/681792 goes in.
DownloadCmd: []string{"pull --overwrite"},
TagCmd: []tagCmd{{"tags", `^(\S+)`}},
TagSyncCmd: []string{"update -r {tag}"},
TagSyncDefault: []string{"update -r revno:-1"},
Scheme: []string{"https", "http", "bzr", "bzr+ssh"},
PingCmd: "info -- {scheme}://{repo}",
RemoteRepo: bzrRemoteRepo,
ResolveRepo: bzrResolveRepo,
Status: bzrStatus,
}
func bzrRemoteRepo(vcsBzr *Cmd, rootDir string) (remoteRepo string, err error) {
outb, err := vcsBzr.runOutput(rootDir, "config parent_location")
if err != nil {
return "", err
}
return strings.TrimSpace(string(outb)), nil
}
func bzrResolveRepo(vcsBzr *Cmd, rootDir, remoteRepo string) (realRepo string, err error) {
outb, err := vcsBzr.runOutput(rootDir, "info "+remoteRepo)
if err != nil {
return "", err
}
out := string(outb)
// Expect:
// ...
// (branch root|repository branch): <URL>
// ...
found := false
for _, prefix := range []string{"\n branch root: ", "\n repository branch: "} {
i := strings.Index(out, prefix)
if i >= 0 {
out = out[i+len(prefix):]
found = true
break
}
}
if !found {
return "", fmt.Errorf("unable to parse output of bzr info")
}
i := strings.Index(out, "\n")
if i < 0 {
return "", fmt.Errorf("unable to parse output of bzr info")
}
out = out[:i]
return strings.TrimSpace(out), nil
Scheme: []string{"https", "http", "bzr", "bzr+ssh"},
PingCmd: "info -- {scheme}://{repo}",
Status: bzrStatus,
}
func bzrStatus(vcsBzr *Cmd, rootDir string) (Status, error) {
@@ -490,45 +336,11 @@ var vcsSvn = &Cmd{
{filename: ".svn", isDir: true},
},
CreateCmd: []string{"checkout -- {repo} {dir}"},
DownloadCmd: []string{"update"},
// There is no tag command in subversion.
// The branch information is all in the path names.
Scheme: []string{"https", "http", "svn", "svn+ssh"},
PingCmd: "info -- {scheme}://{repo}",
RemoteRepo: svnRemoteRepo,
}
func svnRemoteRepo(vcsSvn *Cmd, rootDir string) (remoteRepo string, err error) {
outb, err := vcsSvn.runOutput(rootDir, "info")
if err != nil {
return "", err
}
out := string(outb)
// Expect:
//
// ...
// URL: <URL>
// ...
//
// Note that we're not using the Repository Root line,
// because svn allows checking out subtrees.
// The URL will be the URL of the subtree (what we used with 'svn co')
// while the Repository Root may be a much higher parent.
i := strings.Index(out, "\nURL: ")
if i < 0 {
return "", fmt.Errorf("unable to parse output of svn info")
}
out = out[i+len("\nURL: "):]
i = strings.Index(out, "\n")
if i < 0 {
return "", fmt.Errorf("unable to parse output of svn info")
}
out = out[:i]
return strings.TrimSpace(out), nil
Scheme: []string{"https", "http", "svn", "svn+ssh"},
PingCmd: "info -- {scheme}://{repo}",
}
// fossilRepoName is the name go get associates with a fossil repository. In the
@@ -544,24 +356,8 @@ var vcsFossil = &Cmd{
{filename: "_FOSSIL_", isDir: false},
},
CreateCmd: []string{"-go-internal-mkdir {dir} clone -- {repo} " + filepath.Join("{dir}", fossilRepoName), "-go-internal-cd {dir} open .fossil"},
DownloadCmd: []string{"up"},
TagCmd: []tagCmd{{"tag ls", `(.*)`}},
TagSyncCmd: []string{"up tag:{tag}"},
TagSyncDefault: []string{"up trunk"},
Scheme: []string{"https", "http"},
RemoteRepo: fossilRemoteRepo,
Status: fossilStatus,
}
func fossilRemoteRepo(vcsFossil *Cmd, rootDir string) (remoteRepo string, err error) {
out, err := vcsFossil.runOutput(rootDir, "remote-url")
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
Scheme: []string{"https", "http"},
Status: fossilStatus,
}
var errFossilInfo = errors.New("unable to parse output of fossil info")
@@ -662,7 +458,7 @@ func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([
args[i] = expand(m, arg)
}
if len(args) >= 2 && args[0] == "-go-internal-mkdir" {
if len(args) >= 2 && args[0] == "--go-internal-mkdir" {
var err error
if filepath.IsAbs(args[1]) {
err = os.Mkdir(args[1], fs.ModePerm)
@@ -675,7 +471,7 @@ func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([
args = args[2:]
}
if len(args) >= 2 && args[0] == "-go-internal-cd" {
if len(args) >= 2 && args[0] == "--go-internal-cd" {
if filepath.IsAbs(args[1]) {
dir = args[1]
} else {
@@ -736,99 +532,6 @@ func (v *Cmd) Ping(scheme, repo string) error {
return v.runVerboseOnly(dir, v.PingCmd, "scheme", scheme, "repo", repo)
}
// Create creates a new copy of repo in dir.
// The parent of dir must exist; dir must not.
func (v *Cmd) Create(dir, repo string) error {
release, err := base.AcquireNet()
if err != nil {
return err
}
defer release()
for _, cmd := range v.CreateCmd {
if err := v.run(filepath.Dir(dir), cmd, "dir", dir, "repo", repo); err != nil {
return err
}
}
return nil
}
// Download downloads any new changes for the repo in dir.
func (v *Cmd) Download(dir string) error {
release, err := base.AcquireNet()
if err != nil {
return err
}
defer release()
for _, cmd := range v.DownloadCmd {
if err := v.run(dir, cmd); err != nil {
return err
}
}
return nil
}
// Tags returns the list of available tags for the repo in dir.
func (v *Cmd) Tags(dir string) ([]string, error) {
var tags []string
for _, tc := range v.TagCmd {
out, err := v.runOutput(dir, tc.cmd)
if err != nil {
return nil, err
}
re := regexp.MustCompile(`(?m-s)` + tc.pattern)
for _, m := range re.FindAllStringSubmatch(string(out), -1) {
tags = append(tags, m[1])
}
}
return tags, nil
}
// TagSync syncs the repo in dir to the named tag,
// which either is a tag returned by tags or is v.tagDefault.
func (v *Cmd) TagSync(dir, tag string) error {
if v.TagSyncCmd == nil {
return nil
}
if tag != "" {
for _, tc := range v.TagLookupCmd {
out, err := v.runOutput(dir, tc.cmd, "tag", tag)
if err != nil {
return err
}
re := regexp.MustCompile(`(?m-s)` + tc.pattern)
m := re.FindStringSubmatch(string(out))
if len(m) > 1 {
tag = m[1]
break
}
}
}
release, err := base.AcquireNet()
if err != nil {
return err
}
defer release()
if tag == "" && v.TagSyncDefault != nil {
for _, cmd := range v.TagSyncDefault {
if err := v.run(dir, cmd); err != nil {
return err
}
}
return nil
}
for _, cmd := range v.TagSyncCmd {
if err := v.run(dir, cmd, "tag", tag); err != nil {
return err
}
}
return nil
}
// A vcsPath describes how to convert an import path into a
// version control system and repository name.
type vcsPath struct {

View File

@@ -278,7 +278,10 @@ func allowedVersionArg(arg string) bool {
// parsePathVersionOptional parses path[@version], using adj to
// describe any errors.
func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
before, after, found := strings.Cut(arg, "@")
before, after, found, err := modload.ParsePathVersion(arg)
if err != nil {
return "", "", err
}
if !found {
path = arg
} else {

View File

@@ -920,10 +920,6 @@ const maxSessionTicketLifetime = 7 * 24 * time.Hour
// Clone returns a shallow clone of c or nil if c is nil. It is safe to clone a [Config] that is
// being used concurrently by a TLS client or server.
//
// If Config.SessionTicketKey is unpopulated, and Config.SetSessionTicketKeys has not been
// called, the clone will not share the same auto-rotated session ticket keys as the original
// Config in order to prevent sessions from being resumed across Configs.
func (c *Config) Clone() *Config {
if c == nil {
return nil
@@ -963,8 +959,7 @@ func (c *Config) Clone() *Config {
EncryptedClientHelloRejectionVerify: c.EncryptedClientHelloRejectionVerify,
EncryptedClientHelloKeys: c.EncryptedClientHelloKeys,
sessionTicketKeys: c.sessionTicketKeys,
// We explicitly do not copy autoSessionTicketKeys, so that Configs do
// not share the same auto-rotated keys.
autoSessionTicketKeys: c.autoSessionTicketKeys,
}
}
@@ -1746,3 +1741,31 @@ func fipsAllowCert(c *x509.Certificate) bool {
return false
}
// anyValidVerifiedChain reports if at least one of the chains in verifiedChains
// is valid, as indicated by none of the certificates being expired and the root
// being in opts.Roots (or in the system root pool if opts.Roots is nil). If
// verifiedChains is empty, it returns false.
func anyValidVerifiedChain(verifiedChains [][]*x509.Certificate, opts x509.VerifyOptions) bool {
for _, chain := range verifiedChains {
if len(chain) == 0 {
continue
}
if slices.ContainsFunc(chain, func(cert *x509.Certificate) bool {
return opts.CurrentTime.Before(cert.NotBefore) || opts.CurrentTime.After(cert.NotAfter)
}) {
continue
}
// Since we already validated the chain, we only care that it is
// rooted in a CA in CAs, or in the system pool. On platforms where
// we control chain validation (e.g. not Windows or macOS) this is a
// simple lookup in the CertPool internal hash map. On other
// platforms, this may be more expensive, depending on how they
// implement verification of just root certificates.
root := chain[len(chain)-1]
if _, err := root.Verify(opts); err == nil {
return true
}
}
return false
}

View File

@@ -441,9 +441,6 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (
return nil, nil, nil, nil
}
// Check that the cached server certificate is not expired, and that it's
// valid for the ServerName. This should be ensured by the cache key, but
// protect the application from a faulty ClientSessionCache implementation.
if c.config.time().After(session.peerCertificates[0].NotAfter) {
// Expired certificate, delete the entry.
c.config.ClientSessionCache.Put(cacheKey, nil)
@@ -455,6 +452,18 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (
return nil, nil, nil, nil
}
if err := session.peerCertificates[0].VerifyHostname(c.config.ServerName); err != nil {
// This should be ensured by the cache key, but protect the
// application from a faulty ClientSessionCache implementation.
return nil, nil, nil, nil
}
opts := x509.VerifyOptions{
CurrentTime: c.config.time(),
Roots: c.config.RootCAs,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
if !anyValidVerifiedChain(session.verifiedChains, opts) {
// No valid chains, delete the entry.
c.config.ClientSessionCache.Put(cacheKey, nil)
return nil, nil, nil, nil
}
}

View File

@@ -510,16 +510,16 @@ func (hs *serverHandshakeState) checkForResumption() error {
if sessionHasClientCerts && c.config.ClientAuth == NoClientCert {
return nil
}
if sessionHasClientCerts {
now := c.config.time()
for _, c := range sessionState.peerCertificates {
if now.After(c.NotAfter) {
return nil
}
}
if sessionHasClientCerts && c.config.time().After(sessionState.peerCertificates[0].NotAfter) {
return nil
}
opts := x509.VerifyOptions{
CurrentTime: c.config.time(),
Roots: c.config.ClientCAs,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
if sessionHasClientCerts && c.config.ClientAuth >= VerifyClientCertIfGiven &&
len(sessionState.verifiedChains) == 0 {
!anyValidVerifiedChain(sessionState.verifiedChains, opts) {
return nil
}

View File

@@ -2124,7 +2124,7 @@ func TestHandshakeContextHierarchy(t *testing.T) {
}
}
func TestHandshakeChainExpiryResumptionTLS12(t *testing.T) {
func TestHandshakeChainExpiryResumption(t *testing.T) {
t.Run("TLS1.2", func(t *testing.T) {
testHandshakeChainExpiryResumption(t, VersionTLS12)
})
@@ -2135,7 +2135,8 @@ func TestHandshakeChainExpiryResumptionTLS12(t *testing.T) {
func testHandshakeChainExpiryResumption(t *testing.T, version uint16) {
now := time.Now()
createChain := func(leafNotAfter, rootNotAfter time.Time) (certDER []byte, root *x509.Certificate) {
createChain := func(leafNotAfter, rootNotAfter time.Time) (leafDER, expiredLeafDER []byte, root *x509.Certificate) {
tmpl := &x509.Certificate{
Subject: pkix.Name{CommonName: "root"},
NotBefore: rootNotAfter.Add(-time.Hour * 24),
@@ -2159,39 +2160,177 @@ func testHandshakeChainExpiryResumption(t *testing.T, version uint16) {
NotAfter: leafNotAfter,
KeyUsage: x509.KeyUsageDigitalSignature,
}
certDER, err = x509.CreateCertificate(rand.Reader, tmpl, root, &testECDSAPrivateKey.PublicKey, testECDSAPrivateKey)
leafCertDER, err := x509.CreateCertificate(rand.Reader, tmpl, root, &testECDSAPrivateKey.PublicKey, testECDSAPrivateKey)
if err != nil {
t.Fatalf("CreateCertificate: %v", err)
}
tmpl.NotBefore, tmpl.NotAfter = leafNotAfter.Add(-time.Hour*24*365), leafNotAfter.Add(-time.Hour*24*364)
expiredLeafDERCertDER, err := x509.CreateCertificate(rand.Reader, tmpl, root, &testECDSAPrivateKey.PublicKey, testECDSAPrivateKey)
if err != nil {
t.Fatalf("CreateCertificate: %v", err)
}
return certDER, root
return leafCertDER, expiredLeafDERCertDER, root
}
testExpiration := func(name string, leafNotAfter, rootNotAfter time.Time) {
t.Run(name, func(t *testing.T) {
initialLeafDER, expiredLeafDER, initialRoot := createChain(leafNotAfter, rootNotAfter)
serverConfig := testConfig.Clone()
serverConfig.MaxVersion = version
serverConfig.Certificates = []Certificate{{
Certificate: [][]byte{initialLeafDER, expiredLeafDER},
PrivateKey: testECDSAPrivateKey,
}}
serverConfig.ClientCAs = x509.NewCertPool()
serverConfig.ClientCAs.AddCert(initialRoot)
serverConfig.ClientAuth = RequireAndVerifyClientCert
serverConfig.Time = func() time.Time {
return now
}
serverConfig.InsecureSkipVerify = false
serverConfig.ServerName = "expired-resume.example.com"
clientConfig := testConfig.Clone()
clientConfig.MaxVersion = version
clientConfig.Certificates = []Certificate{{
Certificate: [][]byte{initialLeafDER, expiredLeafDER},
PrivateKey: testECDSAPrivateKey,
}}
clientConfig.RootCAs = x509.NewCertPool()
clientConfig.RootCAs.AddCert(initialRoot)
clientConfig.ServerName = "expired-resume.example.com"
clientConfig.ClientSessionCache = NewLRUClientSessionCache(32)
clientConfig.InsecureSkipVerify = false
clientConfig.ServerName = "expired-resume.example.com"
clientConfig.Time = func() time.Time {
return now
}
testResume := func(t *testing.T, sc, cc *Config, expectResume bool) {
t.Helper()
ss, cs, err := testHandshake(t, cc, sc)
if err != nil {
t.Fatalf("handshake: %v", err)
}
if cs.DidResume != expectResume {
t.Fatalf("DidResume = %v; want %v", cs.DidResume, expectResume)
}
if ss.DidResume != expectResume {
t.Fatalf("DidResume = %v; want %v", cs.DidResume, expectResume)
}
}
testResume(t, serverConfig, clientConfig, false)
testResume(t, serverConfig, clientConfig, true)
expiredNow := time.Unix(0, min(leafNotAfter.UnixNano(), rootNotAfter.UnixNano())).Add(time.Minute)
freshLeafDER, expiredLeafDER, freshRoot := createChain(expiredNow.Add(time.Hour), expiredNow.Add(time.Hour))
clientConfig.Certificates = []Certificate{{
Certificate: [][]byte{freshLeafDER, expiredLeafDER},
PrivateKey: testECDSAPrivateKey,
}}
serverConfig.Time = func() time.Time {
return expiredNow
}
serverConfig.ClientCAs = x509.NewCertPool()
serverConfig.ClientCAs.AddCert(freshRoot)
testResume(t, serverConfig, clientConfig, false)
})
}
initialLeafDER, initialRoot := createChain(now.Add(time.Hour), now.Add(2*time.Hour))
testExpiration("LeafExpiresBeforeRoot", now.Add(2*time.Hour), now.Add(3*time.Hour))
testExpiration("LeafExpiresAfterRoot", now.Add(2*time.Hour), now.Add(time.Hour))
}
func TestHandshakeGetConfigForClientDifferentClientCAs(t *testing.T) {
t.Run("TLS1.2", func(t *testing.T) {
testHandshakeGetConfigForClientDifferentClientCAs(t, VersionTLS12)
})
t.Run("TLS1.3", func(t *testing.T) {
testHandshakeGetConfigForClientDifferentClientCAs(t, VersionTLS13)
})
}
func testHandshakeGetConfigForClientDifferentClientCAs(t *testing.T, version uint16) {
now := time.Now()
tmpl := &x509.Certificate{
Subject: pkix.Name{CommonName: "root"},
NotBefore: now.Add(-time.Hour * 24),
NotAfter: now.Add(time.Hour * 24),
IsCA: true,
BasicConstraintsValid: true,
}
rootDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &testECDSAPrivateKey.PublicKey, testECDSAPrivateKey)
if err != nil {
t.Fatalf("CreateCertificate: %v", err)
}
rootA, err := x509.ParseCertificate(rootDER)
if err != nil {
t.Fatalf("ParseCertificate: %v", err)
}
rootDER, err = x509.CreateCertificate(rand.Reader, tmpl, tmpl, &testECDSAPrivateKey.PublicKey, testECDSAPrivateKey)
if err != nil {
t.Fatalf("CreateCertificate: %v", err)
}
rootB, err := x509.ParseCertificate(rootDER)
if err != nil {
t.Fatalf("ParseCertificate: %v", err)
}
tmpl = &x509.Certificate{
Subject: pkix.Name{},
DNSNames: []string{"example.com"},
NotBefore: now.Add(-time.Hour * 24),
NotAfter: now.Add(time.Hour * 24),
KeyUsage: x509.KeyUsageDigitalSignature,
}
certDER, err := x509.CreateCertificate(rand.Reader, tmpl, rootA, &testECDSAPrivateKey.PublicKey, testECDSAPrivateKey)
if err != nil {
t.Fatalf("CreateCertificate: %v", err)
}
serverConfig := testConfig.Clone()
serverConfig.MaxVersion = version
serverConfig.Certificates = []Certificate{{
Certificate: [][]byte{initialLeafDER},
Certificate: [][]byte{certDER},
PrivateKey: testECDSAPrivateKey,
}}
serverConfig.ClientCAs = x509.NewCertPool()
serverConfig.ClientCAs.AddCert(initialRoot)
serverConfig.ClientAuth = RequireAndVerifyClientCert
serverConfig.Time = func() time.Time {
return now
}
serverConfig.ClientCAs = x509.NewCertPool()
serverConfig.ClientCAs.AddCert(rootA)
serverConfig.ClientAuth = RequireAndVerifyClientCert
switchConfig := false
serverConfig.GetConfigForClient = func(clientHello *ClientHelloInfo) (*Config, error) {
if !switchConfig {
return nil, nil
}
cfg := serverConfig.Clone()
cfg.ClientCAs = x509.NewCertPool()
cfg.ClientCAs.AddCert(rootB)
return cfg, nil
}
serverConfig.InsecureSkipVerify = false
serverConfig.ServerName = "example.com"
clientConfig := testConfig.Clone()
clientConfig.MaxVersion = version
clientConfig.Certificates = []Certificate{{
Certificate: [][]byte{initialLeafDER},
Certificate: [][]byte{certDER},
PrivateKey: testECDSAPrivateKey,
}}
clientConfig.RootCAs = x509.NewCertPool()
clientConfig.RootCAs.AddCert(initialRoot)
clientConfig.ServerName = "expired-resume.example.com"
clientConfig.ClientSessionCache = NewLRUClientSessionCache(32)
clientConfig.RootCAs = x509.NewCertPool()
clientConfig.RootCAs.AddCert(rootA)
clientConfig.Time = func() time.Time {
return now
}
clientConfig.InsecureSkipVerify = false
clientConfig.ServerName = "example.com"
testResume := func(t *testing.T, sc, cc *Config, expectResume bool) {
t.Helper()
@@ -2210,16 +2349,112 @@ func testHandshakeChainExpiryResumption(t *testing.T, version uint16) {
testResume(t, serverConfig, clientConfig, false)
testResume(t, serverConfig, clientConfig, true)
freshLeafDER, freshRoot := createChain(now.Add(2*time.Hour), now.Add(3*time.Hour))
clientConfig.Certificates = []Certificate{{
Certificate: [][]byte{freshLeafDER},
// Cause GetConfigForClient to return a config cloned from the base config,
// but with a different ClientCAs pool. This should cause resumption to fail.
switchConfig = true
testResume(t, serverConfig, clientConfig, false)
testResume(t, serverConfig, clientConfig, true)
}
func TestHandshakeChangeRootCAsResumption(t *testing.T) {
t.Run("TLS1.2", func(t *testing.T) {
testHandshakeChangeRootCAsResumption(t, VersionTLS12)
})
t.Run("TLS1.3", func(t *testing.T) {
testHandshakeChangeRootCAsResumption(t, VersionTLS13)
})
}
func testHandshakeChangeRootCAsResumption(t *testing.T, version uint16) {
now := time.Now()
tmpl := &x509.Certificate{
Subject: pkix.Name{CommonName: "root"},
NotBefore: now.Add(-time.Hour * 24),
NotAfter: now.Add(time.Hour * 24),
IsCA: true,
BasicConstraintsValid: true,
}
rootDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &testECDSAPrivateKey.PublicKey, testECDSAPrivateKey)
if err != nil {
t.Fatalf("CreateCertificate: %v", err)
}
rootA, err := x509.ParseCertificate(rootDER)
if err != nil {
t.Fatalf("ParseCertificate: %v", err)
}
rootDER, err = x509.CreateCertificate(rand.Reader, tmpl, tmpl, &testECDSAPrivateKey.PublicKey, testECDSAPrivateKey)
if err != nil {
t.Fatalf("CreateCertificate: %v", err)
}
rootB, err := x509.ParseCertificate(rootDER)
if err != nil {
t.Fatalf("ParseCertificate: %v", err)
}
tmpl = &x509.Certificate{
Subject: pkix.Name{},
DNSNames: []string{"example.com"},
NotBefore: now.Add(-time.Hour * 24),
NotAfter: now.Add(time.Hour * 24),
KeyUsage: x509.KeyUsageDigitalSignature,
}
certDER, err := x509.CreateCertificate(rand.Reader, tmpl, rootA, &testECDSAPrivateKey.PublicKey, testECDSAPrivateKey)
if err != nil {
t.Fatalf("CreateCertificate: %v", err)
}
serverConfig := testConfig.Clone()
serverConfig.MaxVersion = version
serverConfig.Certificates = []Certificate{{
Certificate: [][]byte{certDER},
PrivateKey: testECDSAPrivateKey,
}}
serverConfig.Time = func() time.Time {
return now.Add(1*time.Hour + 30*time.Minute)
return now
}
serverConfig.ClientCAs = x509.NewCertPool()
serverConfig.ClientCAs.AddCert(freshRoot)
serverConfig.ClientCAs.AddCert(rootA)
serverConfig.ClientAuth = RequireAndVerifyClientCert
serverConfig.InsecureSkipVerify = false
serverConfig.ServerName = "example.com"
clientConfig := testConfig.Clone()
clientConfig.MaxVersion = version
clientConfig.Certificates = []Certificate{{
Certificate: [][]byte{certDER},
PrivateKey: testECDSAPrivateKey,
}}
clientConfig.ClientSessionCache = NewLRUClientSessionCache(32)
clientConfig.RootCAs = x509.NewCertPool()
clientConfig.RootCAs.AddCert(rootA)
clientConfig.Time = func() time.Time {
return now
}
clientConfig.InsecureSkipVerify = false
clientConfig.ServerName = "example.com"
testResume := func(t *testing.T, sc, cc *Config, expectResume bool) {
t.Helper()
ss, cs, err := testHandshake(t, cc, sc)
if err != nil {
t.Fatalf("handshake: %v", err)
}
if cs.DidResume != expectResume {
t.Fatalf("DidResume = %v; want %v", cs.DidResume, expectResume)
}
if ss.DidResume != expectResume {
t.Fatalf("DidResume = %v; want %v", cs.DidResume, expectResume)
}
}
testResume(t, serverConfig, clientConfig, false)
testResume(t, serverConfig, clientConfig, true)
clientConfig = clientConfig.Clone()
clientConfig.RootCAs = x509.NewCertPool()
clientConfig.RootCAs.AddCert(rootB)
testResume(t, serverConfig, clientConfig, false)
testResume(t, serverConfig, clientConfig, true)
}

View File

@@ -15,6 +15,7 @@ import (
"crypto/internal/hpke"
"crypto/rsa"
"crypto/tls/internal/fips140tls"
"crypto/x509"
"errors"
"hash"
"internal/byteorder"
@@ -352,7 +353,6 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
return nil
}
pskIdentityLoop:
for i, identity := range hs.clientHello.pskIdentities {
if i >= maxClientPSKIdentities {
break
@@ -405,16 +405,16 @@ pskIdentityLoop:
if sessionHasClientCerts && c.config.ClientAuth == NoClientCert {
continue
}
if sessionHasClientCerts {
now := c.config.time()
for _, c := range sessionState.peerCertificates {
if now.After(c.NotAfter) {
continue pskIdentityLoop
}
}
if sessionHasClientCerts && c.config.time().After(sessionState.peerCertificates[0].NotAfter) {
continue
}
opts := x509.VerifyOptions{
CurrentTime: c.config.time(),
Roots: c.config.ClientCAs,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
if sessionHasClientCerts && c.config.ClientAuth >= VerifyClientCertIfGiven &&
len(sessionState.verifiedChains) == 0 {
!anyValidVerifiedChain(sessionState.verifiedChains, opts) {
continue
}

View File

@@ -930,8 +930,8 @@ func TestCloneNonFuncFields(t *testing.T) {
}
}
// Set the unexported fields related to session ticket keys, which are copied with Clone().
c1.autoSessionTicketKeys = []ticketKey{c1.ticketKeyFromBytes(c1.SessionTicketKey)}
c1.sessionTicketKeys = []ticketKey{c1.ticketKeyFromBytes(c1.SessionTicketKey)}
// We explicitly don't copy autoSessionTicketKeys in Clone, so don't set it.
c2 := c1.Clone()
if !reflect.DeepEqual(&c1, c2) {
@@ -2249,12 +2249,3 @@ func TestECH(t *testing.T) {
t.Fatal("unexpected certificate")
}
}
func TestConfigCloneAutoSessionTicketKeys(t *testing.T) {
orig := &Config{}
orig.ticketKeys(nil)
clone := orig.Clone()
if slices.Equal(orig.autoSessionTicketKeys, clone.autoSessionTicketKeys) {
t.Fatal("autoSessionTicketKeys slice copied in Clone")
}
}

View File

@@ -1658,6 +1658,22 @@ var nameConstraintsTests = []nameConstraintsTest{
},
expectedError: "\"*.example.com\" is not permitted",
},
// #89: a TLD constraint doesn't exclude unrelated wildcards
{
roots: []constraintsSpec{
{
bad: []string{"dns:tld"},
},
},
intermediates: [][]constraintsSpec{
{
{},
},
},
leaf: leafSpec{
sans: []string{"dns:*.example.com"},
},
},
}
func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) {

View File

@@ -546,7 +546,7 @@ func matchDomainConstraint(domain, constraint string, excluded bool, reversedDom
return false, nil
}
if excluded && wildcardDomain && len(domainLabels) > 1 && len(constraintLabels) > 0 {
if excluded && wildcardDomain && len(domainLabels) > 1 && len(constraintLabels) > 1 {
domainLabels = domainLabels[:len(domainLabels)-1]
constraintLabels = constraintLabels[:len(constraintLabels)-1]
}