mirror of
https://github.com/golang/go.git
synced 2026-01-29 07:02:05 +03:00
Compare commits
6 Commits
63a1b82d1e
...
cb75daf3b2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb75daf3b2 | ||
|
|
5f07b226f9 | ||
|
|
b2abaab3fc | ||
|
|
73fe85f0ea | ||
|
|
2c4733c609 | ||
|
|
14d0bb39c1 |
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user