import (
"bytes"
- "context"
"debug/elf"
"debug/macho"
"debug/pe"
var testTmpDir string
var testBin string
- // testCtx is canceled when the test binary is about to time out.
- //
- // If https://golang.org/issue/28135 is accepted, uses of this variable in test
- // functions should be replaced by t.Context().
- var testCtx = context.Background()
-
// The TestMain function creates a go command for testing purposes and
// deletes it after the tests have been run.
func TestMain(m *testing.M) {
fmt.Printf("SKIP\n")
return
}
- os.Unsetenv("GOROOT_FINAL")
flag.Parse()
- timeoutFlag := flag.Lookup("test.timeout")
- if timeoutFlag != nil {
- // TODO(golang.org/issue/28147): The go command does not pass the
- // test.timeout flag unless either -timeout or -test.timeout is explicitly
- // set on the command line.
- if d := timeoutFlag.Value.(flag.Getter).Get().(time.Duration); d != 0 {
- aBitShorter := d * 95 / 100
- var cancel context.CancelFunc
- testCtx, cancel = context.WithTimeout(testCtx, aBitShorter)
- defer cancel()
- }
- }
-
if *proxyAddr != "" {
StartProxy()
select {}
}
testGOROOT = goEnv("GOROOT")
os.Setenv("TESTGO_GOROOT", testGOROOT)
+ // Ensure that GOROOT is set explicitly.
+ // Otherwise, if the toolchain was built with GOROOT_FINAL set but has not
+ // yet been moved to its final location, programs that invoke runtime.GOROOT
+ // may accidentally use the wrong path.
+ os.Setenv("GOROOT", testGOROOT)
// The whole GOROOT/pkg tree was installed using the GOHOSTOS/GOHOSTARCH
// toolchain (installed in GOROOT/pkg/tool/GOHOSTOS_GOHOSTARCH).
}
testCC = strings.TrimSpace(string(out))
- if out, err := exec.Command(testGo, "env", "CGO_ENABLED").Output(); err != nil {
- fmt.Fprintf(os.Stderr, "running testgo failed: %v\n", err)
+ cmd := exec.Command(testGo, "env", "CGO_ENABLED")
+ cmd.Stderr = new(strings.Builder)
+ if out, err := cmd.Output(); err != nil {
+ fmt.Fprintf(os.Stderr, "running testgo failed: %v\n%s", err, cmd.Stderr)
canRun = false
} else {
canCgo, err = strconv.ParseBool(strings.TrimSpace(string(out)))
// module cache has 0444 directories;
// make them writable in order to remove content.
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return nil // ignore errors walking in file system
- }
- if info.IsDir() {
+ // chmod not only directories, but also things that we couldn't even stat
+ // due to permission errors: they may also be unreadable directories.
+ if err != nil || info.IsDir() {
os.Chmod(path, 0777)
}
return nil
tg.grepStdout("p2: false", "p2 listed as BinaryOnly")
}
-// Issue 16050.
-func TestAlwaysLinkSysoFiles(t *testing.T) {
+// Issue 16050 and 21884.
+func TestLinkSysoFiles(t *testing.T) {
+ if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
+ t.Skip("not linux/amd64")
+ }
+
tg := testgo(t)
defer tg.cleanup()
tg.parallel()
tg.setenv("CGO_ENABLED", "0")
tg.run("list", "-f", "{{.SysoFiles}}", "syso")
tg.grepStdout("a.syso", "missing syso file with CGO_ENABLED=0")
+
+ tg.setenv("CGO_ENABLED", "1")
+ tg.run("list", "-msan", "-f", "{{.SysoFiles}}", "syso")
+ tg.grepStdoutNot("a.syso", "unexpected syso file with -msan")
}
// Issue 16120.
// setLoadPackageDataError returns true if it's safe to load information about
// imported packages, for example, if there was a parse error loading imports
// in one file, but other files are okay.
- func (p *Package) setLoadPackageDataError(err error, path string, stk *ImportStack) {
- // Include the path on the import stack unless the error includes it already.
- errHasPath := false
- if impErr, ok := err.(ImportPathError); ok && impErr.ImportPath() == path {
- errHasPath = true
- } else if matchErr, ok := err.(*search.MatchError); ok && matchErr.Match.Pattern() == path {
- errHasPath = true
+ func (p *Package) setLoadPackageDataError(err error, path string, stk *ImportStack, importPos []token.Position) {
+ matchErr, isMatchErr := err.(*search.MatchError)
+ if isMatchErr && matchErr.Match.Pattern() == path {
if matchErr.Match.IsLiteral() {
// The error has a pattern has a pattern similar to the import path.
// It may be slightly different (./foo matching example.com/foo),
err = matchErr.Err
}
}
- var errStk []string
- if errHasPath {
- errStk = stk.Copy()
- } else {
- stk.Push(path)
- errStk = stk.Copy()
- stk.Pop()
- }
// Replace (possibly wrapped) *build.NoGoError with *load.NoGoError.
// The latter is more specific about the cause.
err = &NoGoError{Package: p}
}
+ // Report the error on the importing package if the problem is with the import declaration
+ // for example, if the package doesn't exist or if the import path is malformed.
+ // On the other hand, don't include a position if the problem is with the imported package,
+ // for example there are no Go files (NoGoError), or there's a problem in the imported
+ // package's source files themselves.
+ //
+ // TODO(matloob): Perhaps make each of those the errors in the first group
+ // (including modload.ImportMissingError, and the corresponding
+ // "cannot find package %q in any of" GOPATH-mode error
+ // produced in build.(*Context).Import; modload.AmbiguousImportError,
+ // and modload.PackageNotInModuleError; and the malformed module path errors
+ // produced in golang.org/x/mod/module.CheckMod) implement an interface
+ // to make it easier to check for them? That would save us from having to
+ // move the modload errors into this package to avoid a package import cycle,
+ // and from having to export an error type for the errors produced in build.
+ if !isMatchErr && nogoErr != nil {
+ stk.Push(path)
+ defer stk.Pop()
+ }
+
// Take only the first error from a scanner.ErrorList. PackageError only
// has room for one position, so we report the first error with a position
// instead of all of the errors without a position.
}
p.Error = &PackageError{
- ImportStack: errStk,
+ ImportStack: stk.Copy(),
Pos: pos,
Err: err,
}
+
+ if path != stk.Top() {
+ p = setErrorPos(p, importPos)
+ }
}
// Resolve returns the resolved version of imports,
p.SwigFiles = pp.SwigFiles
p.SwigCXXFiles = pp.SwigCXXFiles
p.SysoFiles = pp.SysoFiles
+ if cfg.BuildMSan {
+ // There's no way for .syso files to be built both with and without
+ // support for memory sanitizer. Assume they are built without,
+ // and drop them.
+ p.SysoFiles = nil
+ }
p.CgoCFLAGS = pp.CgoCFLAGS
p.CgoCPPFLAGS = pp.CgoCPPFLAGS
p.CgoCXXFLAGS = pp.CgoCXXFLAGS
return append([]string{}, *s...)
}
+ func (s *ImportStack) Top() string {
+ if len(*s) == 0 {
+ return ""
+ }
+ return (*s)[len(*s)-1]
+ }
+
// shorterThan reports whether sp is shorter than t.
// We use this to record the shortest import sequence
// that leads to a particular package.
// Load package.
// loadPackageData may return bp != nil even if an error occurs,
// in order to return partial information.
- p.load(path, stk, bp, err)
- // Add position information unless this is a NoGoError or an ImportCycle error.
- // Import cycles deserve special treatment.
- var g *build.NoGoError
- if p.Error != nil && p.Error.Pos == "" && !errors.As(err, &g) && !p.Error.IsImportCycle {
- p = setErrorPos(p, importPos)
- }
+ p.load(path, stk, importPos, bp, err)
if !cfg.ModulesEnabled && path != cleanImport(path) {
p.Error = &PackageError{
// load populates p using information from bp, err, which should
// be the result of calling build.Context.Import.
// stk contains the import stack, not including path itself.
- func (p *Package) load(path string, stk *ImportStack, bp *build.Package, err error) {
+ func (p *Package) load(path string, stk *ImportStack, importPos []token.Position, bp *build.Package, err error) {
p.copyBuild(bp)
// The localPrefix is the path we interpret ./ imports relative to.
ImportStack: stk.Copy(),
Err: err,
}
+
+ // Add the importer's position information if the import position exists, and
+ // the current package being examined is the importer.
+ // If we have not yet accepted package p onto the import stack,
+ // then the cause of the error is not within p itself: the error
+ // must be either in an explicit command-line argument,
+ // or on the importer side (indicated by a non-empty importPos).
+ if path != stk.Top() && len(importPos) > 0 {
+ p = setErrorPos(p, importPos)
+ }
}
}
if err != nil {
p.Incomplete = true
- p.setLoadPackageDataError(err, path, stk)
+ p.setLoadPackageDataError(err, path, stk, importPos)
}
useBindir := p.Name == "main"
if useBindir {
// Report an error when the old code.google.com/p/go.tools paths are used.
if InstallTargetDir(p) == StalePath {
+ // TODO(matloob): remove this branch, and StalePath itself. code.google.com/p/go is so
+ // old, even this code checking for it is stale now!
newPath := strings.Replace(p.ImportPath, "code.google.com/p/go.", "golang.org/x/", 1)
e := ImportErrorf(p.ImportPath, "the %v command has moved; use %v instead.", p.ImportPath, newPath)
setError(e)
// Report it as a synthetic package.
p := new(Package)
p.ImportPath = m.Pattern()
- var stk ImportStack // empty stack, since the error arose from a pattern, not an import
- p.setLoadPackageDataError(m.Errs[0], m.Pattern(), &stk)
+ // Pass an empty ImportStack and nil importPos: the error arose from a pattern, not an import.
+ var stk ImportStack
+ var importPos []token.Position
+ p.setLoadPackageDataError(m.Errs[0], m.Pattern(), &stk, importPos)
p.Incomplete = true
p.Match = append(p.Match, m.Pattern())
p.Internal.CmdlinePkg = true
pkg := new(Package)
pkg.Internal.Local = true
pkg.Internal.CmdlineFiles = true
- pkg.load("command-line-arguments", &stk, bp, err)
+ pkg.load("command-line-arguments", &stk, nil, bp, err)
pkg.Internal.LocalPrefix = dirToImportPath(dir)
pkg.ImportPath = "command-line-arguments"
pkg.Target = ""
// Inferno utils/8l/asm.c
- // https://bitbucket.org/inferno-os/inferno-os/src/default/utils/8l/asm.c
+ // https://bitbucket.org/inferno-os/inferno-os/src/master/utils/8l/asm.c
//
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
// Data layout and relocation.
// Derived from Inferno utils/6l/l.h
- // https://bitbucket.org/inferno-os/inferno-os/src/default/utils/6l/l.h
+ // https://bitbucket.org/inferno-os/inferno-os/src/master/utils/6l/l.h
//
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
func (ctxt *Link) mkArchSym(which int, name string, ver int, ls *loader.Sym, ss **sym.Symbol) {
if which == BeforeLoadlibFull {
*ls = ctxt.loader.LookupOrCreateSym(name, ver)
+ ctxt.loader.SetAttrReachable(*ls, true)
} else {
*ss = ctxt.loader.Syms[*ls]
}
func (ctxt *Link) mkArchSymVec(which int, name string, ver int, ls []loader.Sym, ss []*sym.Symbol) {
if which == BeforeLoadlibFull {
ls[ver] = ctxt.loader.LookupOrCreateSym(name, ver)
+ ctxt.loader.SetAttrReachable(ls[ver], true)
} else if ls[ver] != 0 {
ss[ver] = ctxt.loader.Syms[ls[ver]]
}
// These packages can use internal linking mode.
// Others trigger external mode.
var internalpkg = []string{
+ "crypto/internal/boring",
"crypto/x509",
"net",
"os/user",
}
}
+ var altLinker string
if ctxt.IsELF && ctxt.DynlinkingGo() {
// We force all symbol resolution to be done at program startup
// because lazy PLT resolution can use large amounts of stack at
// from the beginning of the section (like sym.STYPE).
argv = append(argv, "-Wl,-znocopyreloc")
+ if objabi.GOOS == "android" {
+ // Use lld to avoid errors from default linker (issue #38838)
+ altLinker = "lld"
+ }
+
if ctxt.Arch.InFamily(sys.ARM, sys.ARM64) && objabi.GOOS == "linux" {
// On ARM, the GNU linker will generate COPY relocations
// even with -znocopyreloc set.
// generating COPY relocations.
//
// In both cases, switch to gold.
- argv = append(argv, "-fuse-ld=gold")
+ altLinker = "gold"
// If gold is not installed, gcc will silently switch
// back to ld.bfd. So we parse the version information
}
}
}
-
if ctxt.Arch.Family == sys.ARM64 && objabi.GOOS == "freebsd" {
// Switch to ld.bfd on freebsd/arm64.
- argv = append(argv, "-fuse-ld=bfd")
+ altLinker = "bfd"
// Provide a useful error if ld.bfd is missing.
cmd := exec.Command(*flagExtld, "-fuse-ld=bfd", "-Wl,--version")
}
}
}
+ if altLinker != "" {
+ argv = append(argv, "-fuse-ld="+altLinker)
+ }
if ctxt.IsELF && len(buildinfo) > 0 {
argv = append(argv, fmt.Sprintf("-Wl,--build-id=0x%x", buildinfo))
}
const compressDWARF = "-Wl,--compress-debug-sections=zlib-gnu"
- if ctxt.compressDWARF && linkerFlagSupported(argv[0], compressDWARF) {
+ if ctxt.compressDWARF && linkerFlagSupported(argv[0], altLinker, compressDWARF) {
argv = append(argv, compressDWARF)
}
if ctxt.BuildMode == BuildModeExe && !ctxt.linkShared {
// GCC uses -no-pie, clang uses -nopie.
for _, nopie := range []string{"-no-pie", "-nopie"} {
- if linkerFlagSupported(argv[0], nopie) {
+ if linkerFlagSupported(argv[0], altLinker, nopie) {
argv = append(argv, nopie)
break
}
var createTrivialCOnce sync.Once
- func linkerFlagSupported(linker, flag string) bool {
+ func linkerFlagSupported(linker, altLinker, flag string) bool {
createTrivialCOnce.Do(func() {
src := filepath.Join(*flagTmpdir, "trivial.c")
if err := ioutil.WriteFile(src, []byte("int main() { return 0; }"), 0666); err != nil {
}
}
+ if altLinker != "" {
+ flags = append(flags, "-fuse-ld="+altLinker)
+ }
flags = append(flags, flag, "trivial.c")
cmd := exec.Command(linker, flags...)
Errorf(nil, "cannot open shared library: %s", libpath)
return
}
- defer f.Close()
+ // Keep the file open as decodetypeGcprog needs to read from it.
+ // TODO: fix. Maybe mmap the file.
+ //defer f.Close()
hash, err := readnote(f, ELF_NOTE_GO_NAME, ELF_NOTE_GOABIHASH_TAG)
if err != nil {
"hash"
)
+import "crypto/internal/boring"
+
// FIPS 198-1:
// https://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf
// opad = 0x5c byte repeated for key length
// hmac = H([key ^ opad] H([key ^ ipad] text))
+ // Marshalable is the combination of encoding.BinaryMarshaler and
+ // encoding.BinaryUnmarshaler. Their method definitions are repeated here to
+ // avoid a dependency on the encoding package.
+ type marshalable interface {
+ MarshalBinary() ([]byte, error)
+ UnmarshalBinary([]byte) error
+ }
+
type hmac struct {
- size int
- blocksize int
opad, ipad []byte
outer, inner hash.Hash
+
+ // If marshaled is true, then opad and ipad do not contain a padded
+ // copy of the key, but rather the marshaled state of outer/inner after
+ // opad/ipad has been fed into it.
+ marshaled bool
}
func (h *hmac) Sum(in []byte) []byte {
origLen := len(in)
in = h.inner.Sum(in)
- h.outer.Reset()
- h.outer.Write(h.opad)
+
+ if h.marshaled {
+ if err := h.outer.(marshalable).UnmarshalBinary(h.opad); err != nil {
+ panic(err)
+ }
+ } else {
+ h.outer.Reset()
+ h.outer.Write(h.opad)
+ }
h.outer.Write(in[origLen:])
return h.outer.Sum(in[:origLen])
}
return h.inner.Write(p)
}
- func (h *hmac) Size() int { return h.size }
-
- func (h *hmac) BlockSize() int { return h.blocksize }
+ func (h *hmac) Size() int { return h.outer.Size() }
+ func (h *hmac) BlockSize() int { return h.inner.BlockSize() }
func (h *hmac) Reset() {
+ if h.marshaled {
+ if err := h.inner.(marshalable).UnmarshalBinary(h.ipad); err != nil {
+ panic(err)
+ }
+ return
+ }
+
h.inner.Reset()
h.inner.Write(h.ipad)
+
+ // If the underlying hash is marshalable, we can save some time by
+ // saving a copy of the hash state now, and restoring it on future
+ // calls to Reset and Sum instead of writing ipad/opad every time.
+ //
+ // If either hash is unmarshalable for whatever reason,
+ // it's safe to bail out here.
+ marshalableInner, innerOK := h.inner.(marshalable)
+ if !innerOK {
+ return
+ }
+ marshalableOuter, outerOK := h.outer.(marshalable)
+ if !outerOK {
+ return
+ }
+
+ imarshal, err := marshalableInner.MarshalBinary()
+ if err != nil {
+ return
+ }
+
+ h.outer.Reset()
+ h.outer.Write(h.opad)
+ omarshal, err := marshalableOuter.MarshalBinary()
+ if err != nil {
+ return
+ }
+
+ // Marshaling succeeded; save the marshaled state for later
+ h.ipad = imarshal
+ h.opad = omarshal
+ h.marshaled = true
}
// New returns a new HMAC hash using the given hash.Hash type and key.
// the returned Hash does not implement encoding.BinaryMarshaler
// or encoding.BinaryUnmarshaler.
func New(h func() hash.Hash, key []byte) hash.Hash {
+ if boring.Enabled {
+ hm := boring.NewHMAC(h, key)
+ if hm != nil {
+ return hm
+ }
+ // BoringCrypto did not recognize h, so fall through to standard Go code.
+ }
hm := new(hmac)
hm.outer = h()
hm.inner = h()
- hm.size = hm.inner.Size()
- hm.blocksize = hm.inner.BlockSize()
- hm.ipad = make([]byte, hm.blocksize)
- hm.opad = make([]byte, hm.blocksize)
- if len(key) > hm.blocksize {
+ blocksize := hm.inner.BlockSize()
+ hm.ipad = make([]byte, blocksize)
+ hm.opad = make([]byte, blocksize)
+ if len(key) > blocksize {
// If key is too big, hash it.
hm.outer.Write(key)
key = hm.outer.Sum(nil)
hm.opad[i] ^= 0x5c
}
hm.inner.Write(hm.ipad)
+
return hm
}
package hmac
import (
+ "bytes"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
sha512.Size,
sha512.BlockSize,
},
+ // HMAC without key is dumb but should probably not fail.
+ {
+ sha1.New,
+ []byte{},
+ []byte("message"),
+ "d5d1ed05121417247616cfc8378f360a39da7cfa",
+ sha1.Size,
+ sha1.BlockSize,
+ },
+ {
+ sha256.New,
+ []byte{},
+ []byte("message"),
+ "eb08c1f56d5ddee07f7bdf80468083da06b64cf4fac64fe3a90883df5feacae4",
+ sha256.Size,
+ sha256.BlockSize,
+ },
+ {
+ sha512.New,
+ []byte{},
+ []byte("message"),
+ "08fce52f6395d59c2a3fb8abb281d74ad6f112b9a9c787bcea290d94dadbc82b2ca3e5e12bf2277c7fedbb0154d5493e41bb7459f63c8e39554ea3651b812492",
+ sha512.Size,
+ sha512.BlockSize,
+ },
}
func TestHMAC(t *testing.T) {
if b := h.BlockSize(); b != tt.blocksize {
t.Errorf("BlockSize: got %v, want %v", b, tt.blocksize)
}
- for j := 0; j < 2; j++ {
+ for j := 0; j < 4; j++ {
n, err := h.Write(tt.in)
if n != len(tt.in) || err != nil {
t.Errorf("test %d.%d: Write(%d) = %d, %v", i, j, len(tt.in), n, err)
// Second iteration: make sure reset works.
h.Reset()
+
+ // Third and fourth iteration: make sure hmac works on
+ // hashes without MarshalBinary/UnmarshalBinary
+ if j == 1 {
+ h = New(func() hash.Hash { return justHash{tt.hash()} }, tt.key)
+ }
}
}
}
+
+ // justHash implements just the hash.Hash methods and nothing else
+ type justHash struct {
+ hash.Hash
+ }
func TestEqual(t *testing.T) {
a := []byte("test")
}
}
+func TestWriteAfterSum(t *testing.T) {
+ h := New(sha1.New, nil)
+ h.Write([]byte("hello"))
+ sumHello := h.Sum(nil)
+
+ h = New(sha1.New, nil)
+ h.Write([]byte("hello world"))
+ sumHelloWorld := h.Sum(nil)
+
+ // Test that Sum has no effect on future Sum or Write operations.
+ // This is a bit unusual as far as usage, but it's allowed
+ // by the definition of Go hash.Hash, and some clients expect it to work.
+ h = New(sha1.New, nil)
+ h.Write([]byte("hello"))
+ if sum := h.Sum(nil); !bytes.Equal(sum, sumHello) {
+ t.Fatalf("1st Sum after hello = %x, want %x", sum, sumHello)
+ }
+ if sum := h.Sum(nil); !bytes.Equal(sum, sumHello) {
+ t.Fatalf("2nd Sum after hello = %x, want %x", sum, sumHello)
+ }
+
+ h.Write([]byte(" world"))
+ if sum := h.Sum(nil); !bytes.Equal(sum, sumHelloWorld) {
+ t.Fatalf("1st Sum after hello world = %x, want %x", sum, sumHelloWorld)
+ }
+ if sum := h.Sum(nil); !bytes.Equal(sum, sumHelloWorld) {
+ t.Fatalf("2nd Sum after hello world = %x, want %x", sum, sumHelloWorld)
+ }
+
+ h.Reset()
+ h.Write([]byte("hello"))
+ if sum := h.Sum(nil); !bytes.Equal(sum, sumHello) {
+ t.Fatalf("Sum after Reset + hello = %x, want %x", sum, sumHello)
+ }
+}
+
func BenchmarkHMACSHA256_1K(b *testing.B) {
key := make([]byte, 32)
buf := make([]byte, 1024)
// hash function associated with the Ed25519 signature scheme.
var directSigning crypto.Hash = 0
-// supportedSignatureAlgorithms contains the signature and hash algorithms that
+// defaultSupportedSignatureAlgorithms contains the signature and hash algorithms that
// the code advertises as supported in a TLS 1.2+ ClientHello and in a TLS 1.2+
// CertificateRequest. The two fields are merged to match with TLS 1.3.
// Note that in TLS 1.2, the ECDSA algorithms are not constrained to P-256, etc.
-var supportedSignatureAlgorithms = []SignatureScheme{
+var defaultSupportedSignatureAlgorithms = []SignatureScheme{
PSSWithSHA256,
ECDSAWithP256AndSHA256,
Ed25519,
// ConnectionState records basic TLS details about the connection.
type ConnectionState struct {
- Version uint16 // TLS version used by the connection (e.g. VersionTLS12)
- HandshakeComplete bool // TLS handshake is complete
- DidResume bool // connection resumes a previous TLS connection
- CipherSuite uint16 // cipher suite in use (TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, ...)
- NegotiatedProtocol string // negotiated next protocol (not guaranteed to be from Config.NextProtos)
- NegotiatedProtocolIsMutual bool // negotiated protocol was advertised by server (client side only)
- ServerName string // server name requested by client, if any (server side only)
- PeerCertificates []*x509.Certificate // certificate chain presented by remote peer
- VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates
- SignedCertificateTimestamps [][]byte // SCTs from the peer, if any
- OCSPResponse []byte // stapled OCSP response from peer, if any
+ // Version is the TLS version used by the connection (e.g. VersionTLS12).
+ Version uint16
- // ekm is a closure exposed via ExportKeyingMaterial.
- ekm func(label string, context []byte, length int) ([]byte, error)
+ // HandshakeComplete is true if the handshake has concluded.
+ HandshakeComplete bool
+
+ // DidResume is true if this connection was successfully resumed from a
+ // previous session with a session ticket or similar mechanism.
+ DidResume bool
+
+ // CipherSuite is the cipher suite negotiated for the connection (e.g.
+ // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_AES_128_GCM_SHA256).
+ CipherSuite uint16
+
+ // NegotiatedProtocol is the application protocol negotiated with ALPN.
+ //
+ // Note that on the client side, this is currently not guaranteed to be from
+ // Config.NextProtos.
+ NegotiatedProtocol string
+
+ // NegotiatedProtocolIsMutual used to indicate a mutual NPN negotiation.
+ //
+ // Deprecated: this value is always true.
+ NegotiatedProtocolIsMutual bool
- // TLSUnique contains the "tls-unique" channel binding value (see RFC
- // 5929, section 3). For resumed sessions this value will be nil
- // because resumption does not include enough context (see
- // https://mitls.org/pages/attacks/3SHAKE#channelbindings). This will
- // change in future versions of Go once the TLS master-secret fix has
- // been standardized and implemented. It is not defined in TLS 1.3.
+ // ServerName is the value of the Server Name Indication extension sent by
+ // the client. It's available both on the server and on the client side.
+ ServerName string
+
+ // PeerCertificates are the parsed certificates sent by the peer, in the
+ // order in which they were sent. The first element is the leaf certificate
+ // that the connection is verified against.
+ //
+ // On the client side, it can't be empty. On the server side, it can be
+ // empty if Config.ClientAuth is not RequireAnyClientCert or
+ // RequireAndVerifyClientCert.
+ PeerCertificates []*x509.Certificate
+
+ // VerifiedChains is a list of one or more chains where the first element is
+ // PeerCertificates[0] and the last element is from Config.RootCAs (on the
+ // client side) or Config.ClientCAs (on the server side).
+ //
+ // On the client side, it's set if Config.InsecureSkipVerify is false. On
+ // the server side, it's set if Config.ClientAuth is VerifyClientCertIfGiven
+ // (and the peer provided a certificate) or RequireAndVerifyClientCert.
+ VerifiedChains [][]*x509.Certificate
+
+ // SignedCertificateTimestamps is a list of SCTs provided by the peer
+ // through the TLS handshake for the leaf certificate, if any.
+ SignedCertificateTimestamps [][]byte
+
+ // OCSPResponse is a stapled Online Certificate Status Protocol (OCSP)
+ // response provided by the peer for the leaf certificate, if any.
+ OCSPResponse []byte
+
+ // TLSUnique contains the "tls-unique" channel binding value (see RFC 5929,
+ // Section 3). This value will be nil for TLS 1.3 connections and for all
+ // resumed connections.
+ //
+ // Deprecated: there are conditions in which this value might not be unique
+ // to a connection. See the Security Considerations sections of RFC 5705 and
+ // RFC 7627, and https://mitls.org/pages/attacks/3SHAKE#channelbindings.
TLSUnique []byte
+
+ // ekm is a closure exposed via ExportKeyingMaterial.
+ ekm func(label string, context []byte, length int) ([]byte, error)
}
// ExportKeyingMaterial returns length bytes of exported key material in a new
serverCertificates []*x509.Certificate // Certificate chain presented by the server
verifiedChains [][]*x509.Certificate // Certificate chains we built for verification
receivedAt time.Time // When the session ticket was received from the server
+ ocspResponse []byte // Stapled OCSP response presented by the server
+ scts [][]byte // SCTs presented by the server
// TLS 1.3 fields.
nonce []byte // Ticket nonce sent by the server, to derive PSK
// If GetConfigForClient is nil, the Config passed to Server() will be
// used for all connections.
//
- // Uniquely for the fields in the returned Config, session ticket keys
- // will be duplicated from the original Config if not set.
- // Specifically, if SetSessionTicketKeys was called on the original
- // config but not on the returned config then the ticket keys from the
- // original config will be copied into the new config before use.
- // Otherwise, if SessionTicketKey was set in the original config but
- // not in the returned config then it will be copied into the returned
- // config before use. If neither of those cases applies then the key
- // material from the returned config will be used for session tickets.
+ // If SessionTicketKey was explicitly set on the returned Config, or if
+ // SetSessionTicketKeys was called on the returned Config, those keys will
+ // be used. Otherwise, the original Config keys will be used (and possibly
+ // rotated if they are automatically managed).
GetConfigForClient func(*ClientHelloInfo) (*Config, error)
// VerifyPeerCertificate, if not nil, is called after normal
// be considered but the verifiedChains argument will always be nil.
VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
+ // VerifyConnection, if not nil, is called after normal certificate
+ // verification and after VerifyPeerCertificate by either a TLS client
+ // or server. If it returns a non-nil error, the handshake is aborted
+ // and that error results.
+ //
+ // If normal verification fails then the handshake will abort before
+ // considering this callback. This callback will run for all connections
+ // regardless of InsecureSkipVerify or ClientAuth settings.
+ VerifyConnection func(ConnectionState) error
+
// RootCAs defines the set of root certificate authorities
// that clients use when verifying server certificates.
// If RootCAs is nil, TLS uses the host's root CA set.
// See RFC 5077 and the PSK mode of RFC 8446. If zero, it will be filled
// with random data before the first server handshake.
//
- // If multiple servers are terminating connections for the same host
- // they should all have the same SessionTicketKey. If the
- // SessionTicketKey leaks, previously recorded and future TLS
- // connections using that key might be compromised.
+ // Deprecated: if this field is left at zero, session ticket keys will be
+ // automatically rotated every day and dropped after seven days. For
+ // customizing the rotation schedule or synchronizing servers that are
+ // terminating connections for the same host, use SetSessionTicketKeys.
SessionTicketKey [32]byte
// ClientSessionCache is a cache of ClientSessionState entries for TLS
// used for debugging.
KeyLogWriter io.Writer
- serverInitOnce sync.Once // guards calling (*Config).serverInit
-
- // mutex protects sessionTicketKeys.
+ // mutex protects sessionTicketKeys and autoSessionTicketKeys.
mutex sync.RWMutex
- // sessionTicketKeys contains zero or more ticket keys. If the length
- // is zero, SessionTicketsDisabled must be true. The first key is used
- // for new tickets and any subsequent keys can be used to decrypt old
- // tickets.
+ // sessionTicketKeys contains zero or more ticket keys. If set, it means the
+ // the keys were set with SessionTicketKey or SetSessionTicketKeys. The
+ // first key is used for new tickets and any subsequent keys can be used to
+ // decrypt old tickets. The slice contents are not protected by the mutex
+ // and are immutable.
sessionTicketKeys []ticketKey
+ // autoSessionTicketKeys is like sessionTicketKeys but is owned by the
+ // auto-rotation logic. See Config.ticketKeys.
+ autoSessionTicketKeys []ticketKey
}
- // ticketKeyNameLen is the number of bytes of identifier that is prepended to
- // an encrypted session ticket in order to identify the key used to encrypt it.
- const ticketKeyNameLen = 16
+ const (
+ // ticketKeyNameLen is the number of bytes of identifier that is prepended to
+ // an encrypted session ticket in order to identify the key used to encrypt it.
+ ticketKeyNameLen = 16
+
+ // ticketKeyLifetime is how long a ticket key remains valid and can be used to
+ // resume a client connection.
+ ticketKeyLifetime = 7 * 24 * time.Hour // 7 days
+
+ // ticketKeyRotation is how often the server should rotate the session ticket key
+ // that is used for new tickets.
+ ticketKeyRotation = 24 * time.Hour
+ )
// ticketKey is the internal representation of a session ticket key.
type ticketKey struct {
keyName [ticketKeyNameLen]byte
aesKey [16]byte
hmacKey [16]byte
+ // created is the time at which this ticket key was created. See Config.ticketKeys.
+ created time.Time
}
// ticketKeyFromBytes converts from the external representation of a session
// ticket key to a ticketKey. Externally, session ticket keys are 32 random
// bytes and this function expands that into sufficient name and key material.
- func ticketKeyFromBytes(b [32]byte) (key ticketKey) {
+ func (c *Config) ticketKeyFromBytes(b [32]byte) (key ticketKey) {
hashed := sha512.Sum512(b[:])
copy(key.keyName[:], hashed[:ticketKeyNameLen])
copy(key.aesKey[:], hashed[ticketKeyNameLen:ticketKeyNameLen+16])
copy(key.hmacKey[:], hashed[ticketKeyNameLen+16:ticketKeyNameLen+32])
+ key.created = c.time()
return key
}
// Clone returns a shallow clone of c. It is safe to clone a Config that is
// being used concurrently by a TLS client or server.
func (c *Config) Clone() *Config {
- // Running serverInit ensures that it's safe to read
- // SessionTicketsDisabled.
- c.serverInitOnce.Do(func() { c.serverInit(nil) })
-
- var sessionTicketKeys []ticketKey
c.mutex.RLock()
- sessionTicketKeys = c.sessionTicketKeys
- c.mutex.RUnlock()
-
+ defer c.mutex.RUnlock()
return &Config{
Rand: c.Rand,
Time: c.Time,
GetClientCertificate: c.GetClientCertificate,
GetConfigForClient: c.GetConfigForClient,
VerifyPeerCertificate: c.VerifyPeerCertificate,
+ VerifyConnection: c.VerifyConnection,
RootCAs: c.RootCAs,
NextProtos: c.NextProtos,
ServerName: c.ServerName,
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
Renegotiation: c.Renegotiation,
KeyLogWriter: c.KeyLogWriter,
- sessionTicketKeys: sessionTicketKeys,
+ sessionTicketKeys: c.sessionTicketKeys,
+ autoSessionTicketKeys: c.autoSessionTicketKeys,
}
}
- // serverInit is run under c.serverInitOnce to do initialization of c. If c was
- // returned by a GetConfigForClient callback then the argument should be the
- // Config that was passed to Server, otherwise it should be nil.
- func (c *Config) serverInit(originalConfig *Config) {
- if c.SessionTicketsDisabled || len(c.ticketKeys()) != 0 {
+ // deprecatedSessionTicketKey is set as the prefix of SessionTicketKey if it was
+ // randomized for backwards compatibility but is not in use.
+ var deprecatedSessionTicketKey = []byte("DEPRECATED")
+
+ // initLegacySessionTicketKeyRLocked ensures the legacy SessionTicketKey field is
+ // randomized if empty, and that sessionTicketKeys is populated from it otherwise.
+ func (c *Config) initLegacySessionTicketKeyRLocked() {
+ // Don't write if SessionTicketKey is already defined as our deprecated string,
+ // or if it is defined by the user but sessionTicketKeys is already set.
+ if c.SessionTicketKey != [32]byte{} &&
+ (bytes.HasPrefix(c.SessionTicketKey[:], deprecatedSessionTicketKey) || len(c.sessionTicketKeys) > 0) {
return
}
- alreadySet := false
- for _, b := range c.SessionTicketKey {
- if b != 0 {
- alreadySet = true
- break
+ // We need to write some data, so get an exclusive lock and re-check any conditions.
+ c.mutex.RUnlock()
+ defer c.mutex.RLock()
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
+ if c.SessionTicketKey == [32]byte{} {
+ if _, err := io.ReadFull(c.rand(), c.SessionTicketKey[:]); err != nil {
+ panic(fmt.Sprintf("tls: unable to generate random session ticket key: %v", err))
}
+ // Write the deprecated prefix at the beginning so we know we created
+ // it. This key with the DEPRECATED prefix isn't used as an actual
+ // session ticket key, and is only randomized in case the application
+ // reuses it for some reason.
+ copy(c.SessionTicketKey[:], deprecatedSessionTicketKey)
+ } else if !bytes.HasPrefix(c.SessionTicketKey[:], deprecatedSessionTicketKey) && len(c.sessionTicketKeys) == 0 {
+ c.sessionTicketKeys = []ticketKey{c.ticketKeyFromBytes(c.SessionTicketKey)}
}
- if !alreadySet {
- if originalConfig != nil {
- copy(c.SessionTicketKey[:], originalConfig.SessionTicketKey[:])
- } else if _, err := io.ReadFull(c.rand(), c.SessionTicketKey[:]); err != nil {
- c.SessionTicketsDisabled = true
- return
+ }
+
+ // ticketKeys returns the ticketKeys for this connection.
+ // If configForClient has explicitly set keys, those will
+ // be returned. Otherwise, the keys on c will be used and
+ // may be rotated if auto-managed.
+ // During rotation, any expired session ticket keys are deleted from
+ // c.sessionTicketKeys. If the session ticket key that is currently
+ // encrypting tickets (ie. the first ticketKey in c.sessionTicketKeys)
+ // is not fresh, then a new session ticket key will be
+ // created and prepended to c.sessionTicketKeys.
+ func (c *Config) ticketKeys(configForClient *Config) []ticketKey {
+ // If the ConfigForClient callback returned a Config with explicitly set
+ // keys, use those, otherwise just use the original Config.
+ if configForClient != nil {
+ configForClient.mutex.RLock()
+ if configForClient.SessionTicketsDisabled {
+ return nil
}
+ configForClient.initLegacySessionTicketKeyRLocked()
+ if len(configForClient.sessionTicketKeys) != 0 {
+ ret := configForClient.sessionTicketKeys
+ configForClient.mutex.RUnlock()
+ return ret
+ }
+ configForClient.mutex.RUnlock()
}
- if originalConfig != nil {
- originalConfig.mutex.RLock()
- c.sessionTicketKeys = originalConfig.sessionTicketKeys
- originalConfig.mutex.RUnlock()
- } else {
- c.sessionTicketKeys = []ticketKey{ticketKeyFromBytes(c.SessionTicketKey)}
+ c.mutex.RLock()
+ defer c.mutex.RUnlock()
+ if c.SessionTicketsDisabled {
+ return nil
+ }
+ c.initLegacySessionTicketKeyRLocked()
+ if len(c.sessionTicketKeys) != 0 {
+ return c.sessionTicketKeys
+ }
+ // Fast path for the common case where the key is fresh enough.
+ if len(c.autoSessionTicketKeys) > 0 && c.time().Sub(c.autoSessionTicketKeys[0].created) < ticketKeyRotation {
+ return c.autoSessionTicketKeys
}
- }
- func (c *Config) ticketKeys() []ticketKey {
- c.mutex.RLock()
- // c.sessionTicketKeys is constant once created. SetSessionTicketKeys
- // will only update it by replacing it with a new value.
- ret := c.sessionTicketKeys
+ // autoSessionTicketKeys are managed by auto-rotation.
c.mutex.RUnlock()
- return ret
+ defer c.mutex.RLock()
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
+ // Re-check the condition in case it changed since obtaining the new lock.
+ if len(c.autoSessionTicketKeys) == 0 || c.time().Sub(c.autoSessionTicketKeys[0].created) >= ticketKeyRotation {
+ var newKey [32]byte
+ if _, err := io.ReadFull(c.rand(), newKey[:]); err != nil {
+ panic(fmt.Sprintf("unable to generate random session ticket key: %v", err))
+ }
+ valid := make([]ticketKey, 0, len(c.autoSessionTicketKeys)+1)
+ valid = append(valid, c.ticketKeyFromBytes(newKey))
+ for _, k := range c.autoSessionTicketKeys {
+ // While rotating the current key, also remove any expired ones.
+ if c.time().Sub(k.created) < ticketKeyLifetime {
+ valid = append(valid, k)
+ }
+ }
+ c.autoSessionTicketKeys = valid
+ }
+ return c.autoSessionTicketKeys
}
- // SetSessionTicketKeys updates the session ticket keys for a server. The first
- // key will be used when creating new tickets, while all keys can be used for
- // decrypting tickets. It is safe to call this function while the server is
- // running in order to rotate the session ticket keys. The function will panic
- // if keys is empty.
+ // SetSessionTicketKeys updates the session ticket keys for a server.
+ //
+ // The first key will be used when creating new tickets, while all keys can be
+ // used for decrypting tickets. It is safe to call this function while the
+ // server is running in order to rotate the session ticket keys. The function
+ // will panic if keys is empty.
+ //
+ // Calling this function will turn off automatic session ticket key rotation.
+ //
+ // If multiple servers are terminating connections for the same host they should
+ // all have the same session ticket keys. If the session ticket keys leaks,
+ // previously recorded and future TLS connections using those keys might be
+ // compromised.
func (c *Config) SetSessionTicketKeys(keys [][32]byte) {
if len(keys) == 0 {
panic("tls: keys must have at least one key")
newKeys := make([]ticketKey, len(keys))
for i, bytes := range keys {
- newKeys[i] = ticketKeyFromBytes(bytes)
+ newKeys[i] = c.ticketKeyFromBytes(bytes)
}
c.mutex.Lock()
}
func (c *Config) cipherSuites() []uint16 {
+ if needFIPS() {
+ return fipsCipherSuites(c)
+ }
s := c.CipherSuites
if s == nil {
s = defaultCipherSuites()
func (c *Config) supportedVersions() []uint16 {
versions := make([]uint16, 0, len(supportedVersions))
for _, v := range supportedVersions {
+ if needFIPS() && (v < fipsMinVersion(c) || v > fipsMaxVersion(c)) {
+ continue
+ }
if c != nil && c.MinVersion != 0 && v < c.MinVersion {
continue
}
var defaultCurvePreferences = []CurveID{X25519, CurveP256, CurveP384, CurveP521}
func (c *Config) curvePreferences() []CurveID {
+ if needFIPS() {
+ return fipsCurvePreferences(c)
+ }
if c == nil || len(c.CurvePreferences) == 0 {
return defaultCurvePreferences
}
hasGCMAsm = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X
)
- if hasGCMAsm {
+ if hasGCMAsm || boringEnabled {
+ // If BoringCrypto is enabled, always prioritize AES-GCM.
// If AES-GCM hardware is provided then prioritise AES-GCM
// cipher suites.
topCipherSuites = []uint16{
}
if hello.vers >= VersionTLS12 {
- hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms
+ hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms()
+ }
+ if testingOnlyForceClientHelloSignatureAlgorithms != nil {
+ hello.supportedSignatureAlgorithms = testingOnlyForceClientHelloSignatureAlgorithms
}
var params ecdheParameters
if err != nil {
return err
}
+ c.serverName = hello.serverName
cacheKey, session, earlySecret, binderKey := c.loadSession(hello)
if cacheKey != "" && session != nil {
hs.finishedHash.Write(hs.serverHello.marshal())
c.buffering = true
+ c.didResume = isResume
if isResume {
if err := hs.establishKeys(); err != nil {
return err
return err
}
c.clientFinishedIsFirst = false
+ // Make sure the connection is still being verified whether or not this
+ // is a resumption. Resumptions currently don't reverify certificates so
+ // they don't call verifyServerCertificate. See Issue 31641.
+ if c.config.VerifyConnection != nil {
+ if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
+ c.sendAlert(alertBadCertificate)
+ return err
+ }
+ }
if err := hs.sendFinished(c.clientFinished[:]); err != nil {
return err
}
}
c.ekm = ekmFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.hello.random, hs.serverHello.random)
- c.didResume = isResume
atomic.StoreUint32(&c.handshakeStatus, 1)
return nil
}
hs.finishedHash.Write(certMsg.marshal())
- if c.handshakes == 0 {
- // If this is the first handshake on a connection, process and
- // (optionally) verify the server's certificates.
- if err := c.verifyServerCertificate(certMsg.certificates); err != nil {
- return err
- }
- } else {
- // This is a renegotiation handshake. We require that the
- // server's identity (i.e. leaf certificate) is unchanged and
- // thus any previous trust decision is still valid.
- //
- // See https://mitls.org/pages/attacks/3SHAKE for the
- // motivation behind this requirement.
- if !bytes.Equal(c.peerCertificates[0].Raw, certMsg.certificates[0]) {
- c.sendAlert(alertBadCertificate)
- return errors.New("tls: server's identity changed during renegotiation")
- }
- }
-
msg, err = c.readHandshake()
if err != nil {
return err
}
}
+ if c.handshakes == 0 {
+ // If this is the first handshake on a connection, process and
+ // (optionally) verify the server's certificates.
+ if err := c.verifyServerCertificate(certMsg.certificates); err != nil {
+ return err
+ }
+ } else {
+ // This is a renegotiation handshake. We require that the
+ // server's identity (i.e. leaf certificate) is unchanged and
+ // thus any previous trust decision is still valid.
+ //
+ // See https://mitls.org/pages/attacks/3SHAKE for the
+ // motivation behind this requirement.
+ if !bytes.Equal(c.peerCertificates[0].Raw, certMsg.certificates[0]) {
+ c.sendAlert(alertBadCertificate)
+ return errors.New("tls: server's identity changed during renegotiation")
+ }
+ }
+
keyAgreement := hs.suite.ka(c.vers)
skx, ok := msg.(*serverKeyExchangeMsg)
return false, errors.New("tls: server resumed a session with a different cipher suite")
}
- // Restore masterSecret and peerCerts from previous state
+ // Restore masterSecret, peerCerts, and ocspResponse from previous state
hs.masterSecret = hs.session.masterSecret
c.peerCertificates = hs.session.serverCertificates
c.verifiedChains = hs.session.verifiedChains
+ c.ocspResponse = hs.session.ocspResponse
+ // Let the ServerHello SCTs override the session SCTs from the original
+ // connection, if any are provided
+ if len(c.scts) == 0 && len(hs.session.scts) != 0 {
+ c.scts = hs.session.scts
+ }
+
return true, nil
}
serverCertificates: c.peerCertificates,
verifiedChains: c.verifiedChains,
receivedAt: c.config.time(),
+ ocspResponse: c.ocspResponse,
+ scts: c.scts,
}
return nil
if !c.config.InsecureSkipVerify {
opts := x509.VerifyOptions{
+ IsBoring: isBoringCertificate,
+
Roots: c.config.RootCAs,
CurrentTime: c.config.time(),
DNSName: c.config.ServerName,
}
}
- if c.config.VerifyPeerCertificate != nil {
- if err := c.config.VerifyPeerCertificate(certificates, c.verifiedChains); err != nil {
- c.sendAlert(alertBadCertificate)
- return err
- }
- }
-
switch certs[0].PublicKey.(type) {
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
break
c.peerCertificates = certs
+ if c.config.VerifyPeerCertificate != nil {
+ if err := c.config.VerifyPeerCertificate(certificates, c.verifiedChains); err != nil {
+ c.sendAlert(alertBadCertificate)
+ return err
+ }
+ }
+
+ if c.config.VerifyConnection != nil {
+ if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
+ c.sendAlert(alertBadCertificate)
+ return err
+ }
+ }
+
return nil
}
func (hs *clientHandshakeStateTLS13) handshake() error {
c := hs.c
+ if needFIPS() {
+ return errors.New("tls: internal error: TLS 1.3 reached in FIPS mode")
+ }
+
// The server must not select TLS 1.3 in a renegotiation. See RFC 8446,
// sections 4.1.2 and 4.1.3.
if c.handshakes > 0 {
c.didResume = true
c.peerCertificates = hs.session.serverCertificates
c.verifiedChains = hs.session.verifiedChains
+ c.ocspResponse = hs.session.ocspResponse
+ c.scts = hs.session.scts
return nil
}
// Either a PSK or a certificate is always used, but not both.
// See RFC 8446, Section 4.1.1.
if hs.usingPSK {
+ // Make sure the connection is still being verified whether or not this
+ // is a resumption. Resumptions currently don't reverify certificates so
+ // they don't call verifyServerCertificate. See Issue 31641.
+ if c.config.VerifyConnection != nil {
+ if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
+ c.sendAlert(alertBadCertificate)
+ return err
+ }
+ }
return nil
}
}
// See RFC 8446, Section 4.4.3.
- if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms) {
+ if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms()) {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: certificate used with invalid signature algorithm")
}
nonce: msg.nonce,
useBy: c.config.time().Add(lifetime),
ageAdd: msg.ageAdd,
+ ocspResponse: c.ocspResponse,
+ scts: c.scts,
}
cacheKey := clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
}
}
if rand.Intn(10) > 5 {
- m.supportedSignatureAlgorithms = supportedSignatureAlgorithms
+ m.supportedSignatureAlgorithms = supportedSignatureAlgorithms()
}
if rand.Intn(10) > 5 {
- m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms
+ m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms()
}
for i := 0; i < rand.Intn(5); i++ {
m.alpnProtocols = append(m.alpnProtocols, randomString(rand.Intn(20)+1, rand))
s.vers = uint16(rand.Intn(10000))
s.cipherSuite = uint16(rand.Intn(10000))
s.masterSecret = randomBytes(rand.Intn(100)+1, rand)
+ s.createdAt = uint64(rand.Int63())
for i := 0; i < rand.Intn(20); i++ {
s.certificates = append(s.certificates, randomBytes(rand.Intn(500)+1, rand))
}
m.scts = true
}
if rand.Intn(10) > 5 {
- m.supportedSignatureAlgorithms = supportedSignatureAlgorithms
+ m.supportedSignatureAlgorithms = supportedSignatureAlgorithms()
}
if rand.Intn(10) > 5 {
- m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms
+ m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms()
}
if rand.Intn(10) > 5 {
m.certificateAuthorities = make([][]byte, 3)
"fmt"
"io"
"sync/atomic"
+ "time"
)
// serverHandshakeState contains details of a server handshake in progress.
// serverHandshake performs a TLS handshake as a server.
func (c *Conn) serverHandshake() error {
- // If this is the first server handshake, we generate a random key to
- // encrypt the tickets with.
- c.config.serverInitOnce.Do(func() { c.config.serverInit(nil) })
-
clientHello, err := c.readClientHello()
if err != nil {
return err
c.buffering = true
if hs.checkForResumption() {
// The client has included a session ticket and so we do an abbreviated handshake.
+ c.didResume = true
if err := hs.doResumeHandshake(); err != nil {
return err
}
if err := hs.establishKeys(); err != nil {
return err
}
- // ticketSupported is set in a resumption handshake if the
- // ticket from the client was encrypted with an old session
- // ticket key and thus a refreshed ticket should be sent.
- if hs.hello.ticketSupported {
- if err := hs.sendSessionTicket(); err != nil {
- return err
- }
+ if err := hs.sendSessionTicket(); err != nil {
+ return err
}
if err := hs.sendFinished(c.serverFinished[:]); err != nil {
return err
if err := hs.readFinished(nil); err != nil {
return err
}
- c.didResume = true
} else {
// The client didn't include a session ticket, or it wasn't
// valid so we do a full handshake.
return nil, unexpectedMessageError(clientHello, msg)
}
+ var configForClient *Config
+ originalConfig := c.config
if c.config.GetConfigForClient != nil {
chi := clientHelloInfo(c, clientHello)
- if newConfig, err := c.config.GetConfigForClient(chi); err != nil {
+ if configForClient, err = c.config.GetConfigForClient(chi); err != nil {
c.sendAlert(alertInternalError)
return nil, err
- } else if newConfig != nil {
- newConfig.serverInitOnce.Do(func() { newConfig.serverInit(c.config) })
- c.config = newConfig
+ } else if configForClient != nil {
+ c.config = configForClient
}
}
+ c.ticketKeys = originalConfig.ticketKeys(configForClient)
clientVersions := clientHello.supportedVersions
if len(clientHello.supportedVersions) == 0 {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: no cipher suite supported by both client and server")
}
+ c.cipherSuite = hs.suite.id
for _, id := range hs.clientHello.cipherSuites {
if id == TLS_FALLBACK_SCSV {
return false
}
+ createdAt := time.Unix(int64(hs.sessionState.createdAt), 0)
+ if c.config.time().Sub(createdAt) > maxSessionTicketLifetime {
+ return false
+ }
+
// Never resume a session for a different TLS version.
if c.vers != hs.sessionState.vers {
return false
c := hs.c
hs.hello.cipherSuite = hs.suite.id
+ c.cipherSuite = hs.suite.id
// We echo the client's session ID in the ServerHello to let it know
// that we're doing a resumption.
hs.hello.sessionId = hs.clientHello.sessionId
return err
}
+ if c.config.VerifyConnection != nil {
+ if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
+ c.sendAlert(alertBadCertificate)
+ return err
+ }
+ }
+
hs.masterSecret = hs.sessionState.masterSecret
return nil
}
if c.vers >= VersionTLS12 {
certReq.hasSignatureAlgorithm = true
- certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms
+ certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms()
}
// An empty list of certificateAuthorities signals to
return err
}
}
+ if c.config.VerifyConnection != nil {
+ if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
+ c.sendAlert(alertBadCertificate)
+ return err
+ }
+ }
// Get client key exchange
ckx, ok := msg.(*clientKeyExchangeMsg)
}
func (hs *serverHandshakeState) sendSessionTicket() error {
+ // ticketSupported is set in a resumption handshake if the
+ // ticket from the client was encrypted with an old session
+ // ticket key and thus a refreshed ticket should be sent.
if !hs.hello.ticketSupported {
return nil
}
c := hs.c
m := new(newSessionTicketMsg)
+ createdAt := uint64(c.config.time().Unix())
+ if hs.sessionState != nil {
+ // If this is re-wrapping an old key, then keep
+ // the original time it was created.
+ createdAt = hs.sessionState.createdAt
+ }
+
var certsFromClient [][]byte
for _, cert := range c.peerCertificates {
certsFromClient = append(certsFromClient, cert.Raw)
state := sessionState{
vers: c.vers,
cipherSuite: hs.suite.id,
+ createdAt: createdAt,
masterSecret: hs.masterSecret,
certificates: certsFromClient,
}
return err
}
- c.cipherSuite = hs.suite.id
copy(out, finished.verifyData)
return nil
if c.config.ClientAuth >= VerifyClientCertIfGiven && len(certs) > 0 {
opts := x509.VerifyOptions{
+ IsBoring: isBoringCertificate,
+
Roots: c.config.ClientCAs,
CurrentTime: c.config.time(),
Intermediates: x509.NewCertPool(),
c.verifiedChains = chains
}
+ c.peerCertificates = certs
+ c.ocspResponse = certificate.OCSPStaple
+ c.scts = certificate.SignedCertificateTimestamps
+
+ if len(certs) > 0 {
+ switch certs[0].PublicKey.(type) {
+ case *ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey:
+ default:
+ c.sendAlert(alertUnsupportedCertificate)
+ return fmt.Errorf("tls: client certificate contains an unsupported public key of type %T", certs[0].PublicKey)
+ }
+ }
+
if c.config.VerifyPeerCertificate != nil {
if err := c.config.VerifyPeerCertificate(certificates, c.verifiedChains); err != nil {
c.sendAlert(alertBadCertificate)
}
}
- if len(certs) == 0 {
- return nil
- }
-
- switch certs[0].PublicKey.(type) {
- case *ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey:
- default:
- c.sendAlert(alertUnsupportedCertificate)
- return fmt.Errorf("tls: client certificate contains an unsupported public key of type %T", certs[0].PublicKey)
- }
-
- c.peerCertificates = certs
- c.ocspResponse = certificate.OCSPStaple
- c.scts = certificate.SignedCertificateTimestamps
return nil
}
func (hs *serverHandshakeStateTLS13) handshake() error {
c := hs.c
+ if needFIPS() {
+ return errors.New("tls: internal error: TLS 1.3 reached in FIPS mode")
+ }
+
// For an overview of the TLS 1.3 handshake, see RFC 8446, Section 2.
if err := hs.processClientHello(); err != nil {
return err
return errors.New("tls: invalid PSK binder")
}
+ c.didResume = true
if err := c.processCertsFromClient(sessionState.certificate); err != nil {
return err
}
hs.hello.selectedIdentityPresent = true
hs.hello.selectedIdentity = uint16(i)
hs.usingPSK = true
- c.didResume = true
return nil
}
certReq := new(certificateRequestMsgTLS13)
certReq.ocspStapling = true
certReq.scts = true
- certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms
+ certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms()
if c.config.ClientCAs != nil {
certReq.certificateAuthorities = c.config.ClientCAs.Subjects()
}
c := hs.c
if !hs.requestClientCert() {
+ // Make sure the connection is still being verified whether or not
+ // the server requested a client certificate.
+ if c.config.VerifyConnection != nil {
+ if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
+ c.sendAlert(alertBadCertificate)
+ return err
+ }
+ }
return nil
}
return err
}
+ if c.config.VerifyConnection != nil {
+ if err := c.config.VerifyConnection(c.connectionStateLocked()); err != nil {
+ c.sendAlert(alertBadCertificate)
+ return err
+ }
+ }
+
if len(certMsg.certificate.Certificate) != 0 {
msg, err = c.readHandshake()
if err != nil {
}
// See RFC 8446, Section 4.4.3.
- if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms) {
+ if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, supportedSignatureAlgorithms()) {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: client certificate used with invalid signature algorithm")
}
)
// ignoreCN disables interpreting Common Name as a hostname. See issue 24151.
- var ignoreCN = strings.Contains(os.Getenv("GODEBUG"), "x509ignoreCN=1")
+ var ignoreCN = !strings.Contains(os.Getenv("GODEBUG"), "x509ignoreCN=0")
type InvalidReason int
// contains name constraints, and the Common Name can be interpreted as
// a hostname.
//
- // You can avoid this error by setting the experimental GODEBUG environment
- // variable to "x509ignoreCN=1", disabling Common Name matching entirely.
- // This behavior might become the default in the future.
+ // This error is only returned when legacy Common Name matching is enabled
+ // by setting the GODEBUG environment variable to "x509ignoreCN=1". This
+ // setting might be removed in the future.
NameConstraintsWithoutSANs
// UnconstrainedName results when a CA certificate contains permitted
// name constraints, but leaf certificate contains a name of an
func (h HostnameError) Error() string {
c := h.Certificate
- if !c.hasSANExtension() && !validHostname(c.Subject.CommonName) &&
- matchHostnames(toLowerCaseASCII(c.Subject.CommonName), toLowerCaseASCII(h.Host)) {
- // This would have validated, if it weren't for the validHostname check on Common Name.
- return "x509: Common Name is not a valid hostname: " + c.Subject.CommonName
+ if !c.hasSANExtension() && matchHostnames(c.Subject.CommonName, h.Host) {
+ if !ignoreCN && !validHostnamePattern(c.Subject.CommonName) {
+ // This would have validated, if it weren't for the validHostname check on Common Name.
+ return "x509: Common Name is not a valid hostname: " + c.Subject.CommonName
+ }
+ if ignoreCN && validHostnamePattern(c.Subject.CommonName) {
+ // This would have validated if x509ignoreCN=0 were set.
+ return "x509: certificate relies on legacy Common Name field, " +
+ "use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0"
+ }
}
var valid string
// VerifyOptions contains parameters for Certificate.Verify.
type VerifyOptions struct {
+ // IsBoring is a validity check for BoringCrypto.
+ // If not nil, it will be called to check whether a given certificate
+ // can be used for constructing verification chains.
+ IsBoring func(*Certificate) bool
+
// DNSName, if set, is checked against the leaf certificate with
// Certificate.VerifyHostname.
DNSName string
}
}
+ if opts.IsBoring != nil && !opts.IsBoring(c) {
+ // IncompatibleUsage is not quite right here,
+ // but it's also the "no chains found" error
+ // and is close enough.
+ return CertificateInvalidError{c, IncompatibleUsage, ""}
+ }
+
return nil
}
// the name being validated. Note that DirectoryName constraints are not
// supported.
//
+ // Name constraint validation follows the rules from RFC 5280, with the
+ // addition that DNS name constraints may use the leading period format
+ // defined for emails and URIs. When a constraint has a leading period
+ // it indicates that at least one additional label must be prepended to
+ // the constrained name to be considered valid.
+ //
// Extended Key Usage values are enforced down a chain, so an intermediate or
// root that enumerates EKUs prevents a leaf from asserting an EKU not in that
// list.
return
}
+ func validHostnamePattern(host string) bool { return validHostname(host, true) }
+ func validHostnameInput(host string) bool { return validHostname(host, false) }
+
// validHostname reports whether host is a valid hostname that can be matched or
// matched against according to RFC 6125 2.2, with some leniency to accommodate
// legacy values.
- func validHostname(host string) bool {
- host = strings.TrimSuffix(host, ".")
-
+ func validHostname(host string, isPattern bool) bool {
+ if !isPattern {
+ host = strings.TrimSuffix(host, ".")
+ }
if len(host) == 0 {
return false
}
// Empty label.
return false
}
- if i == 0 && part == "*" {
+ if isPattern && i == 0 && part == "*" {
// Only allow full left-most wildcards, as those are the only ones
// we match, and matching literal '*' characters is probably never
// the expected behavior.
if c == '-' && j != 0 {
continue
}
- if c == '_' || c == ':' {
- // Not valid characters in hostnames, but commonly
+ if c == '_' {
+ // Not a valid character in hostnames, but commonly
// found in deployments outside the WebPKI.
continue
}
// commonNameAsHostname reports whether the Common Name field should be
// considered the hostname that the certificate is valid for. This is a legacy
- // behavior, disabled if the Subject Alt Name extension is present.
+ // behavior, disabled by default or if the Subject Alt Name extension is present.
//
// It applies the strict validHostname check to the Common Name field, so that
// certificates without SANs can still be validated against CAs with name
// constraints if there is no risk the CN would be matched as a hostname.
// See NameConstraintsWithoutSANs and issue 24151.
func (c *Certificate) commonNameAsHostname() bool {
- return !ignoreCN && !c.hasSANExtension() && validHostname(c.Subject.CommonName)
+ return !ignoreCN && !c.hasSANExtension() && validHostnamePattern(c.Subject.CommonName)
+ }
+
+ func matchExactly(hostA, hostB string) bool {
+ if hostA == "" || hostA == "." || hostB == "" || hostB == "." {
+ return false
+ }
+ return toLowerCaseASCII(hostA) == toLowerCaseASCII(hostB)
}
func matchHostnames(pattern, host string) bool {
- host = strings.TrimSuffix(host, ".")
- pattern = strings.TrimSuffix(pattern, ".")
+ pattern = toLowerCaseASCII(pattern)
+ host = toLowerCaseASCII(strings.TrimSuffix(host, "."))
if len(pattern) == 0 || len(host) == 0 {
return false
//
// IP addresses can be optionally enclosed in square brackets and are checked
// against the IPAddresses field. Other names are checked case insensitively
- // against the DNSNames field, with support for only one wildcard as the whole
- // left-most label.
+ // against the DNSNames field. If the names are valid hostnames, the certificate
+ // fields can have a wildcard as the left-most label.
//
- // If the Common Name field is a valid hostname, and the certificate doesn't
- // have any Subject Alternative Names, the name will also be checked against the
- // Common Name. This legacy behavior can be disabled by setting the GODEBUG
- // environment variable to "x509ignoreCN=1" and might be removed in the future.
+ // The legacy Common Name field is ignored unless it's a valid hostname, the
+ // certificate doesn't have any Subject Alternative Names, and the GODEBUG
+ // environment variable is set to "x509ignoreCN=0". Support for Common Name is
+ // deprecated will be entirely removed in the future.
func (c *Certificate) VerifyHostname(h string) error {
// IP addresses may be written in [ ].
candidateIP := h
return HostnameError{c, candidateIP}
}
- lowered := toLowerCaseASCII(h)
-
+ names := c.DNSNames
if c.commonNameAsHostname() {
- if matchHostnames(toLowerCaseASCII(c.Subject.CommonName), lowered) {
- return nil
- }
- } else {
- for _, match := range c.DNSNames {
- if matchHostnames(toLowerCaseASCII(match), lowered) {
+ names = []string{c.Subject.CommonName}
+ }
+
+ candidateName := toLowerCaseASCII(h) // Save allocations inside the loop.
+ validCandidateName := validHostnameInput(candidateName)
+
+ for _, match := range names {
+ // Ideally, we'd only match valid hostnames according to RFC 6125 like
+ // browsers (more or less) do, but in practice Go is used in a wider
+ // array of contexts and can't even assume DNS resolution. Instead,
+ // always allow perfect matches, and only apply wildcard and trailing
+ // dot processing to valid hostnames.
+ if validCandidateName && validHostnamePattern(match) {
+ if matchHostnames(match, candidateName) {
+ return nil
+ }
+ } else {
+ if matchExactly(match, candidateName) {
return nil
}
}
"testing"
)
- // pkgDeps defines the expected dependencies between packages in
+ // depsRules defines the expected dependencies between packages in
// the Go source tree. It is a statement of policy.
- // Changes should not be made to this map without prior discussion.
- //
- // The map contains two kinds of entries:
- // 1) Lower-case keys are standard import paths and list the
- // allowed imports in that package.
- // 2) Upper-case keys define aliases for package sets, which can then
- // be used as dependencies by other rules.
//
// DO NOT CHANGE THIS DATA TO FIX BUILDS.
+ // Existing packages should not have their constraints relaxed
+ // without prior discussion.
+ // Negative assertions should almost never be removed.
//
- var pkgDeps = map[string][]string{
- // L0 is the lowest level, core, nearly unavoidable packages.
- "errors": {"runtime", "internal/reflectlite"},
- "io": {"errors", "sync", "sync/atomic"},
- "runtime": {"unsafe", "runtime/internal/atomic", "runtime/internal/sys", "runtime/internal/math", "internal/cpu", "internal/bytealg"},
- "runtime/internal/sys": {},
- "runtime/internal/atomic": {"unsafe", "internal/cpu"},
- "runtime/internal/math": {"runtime/internal/sys"},
- "internal/race": {"runtime", "unsafe"},
- "sync": {"internal/race", "runtime", "sync/atomic", "unsafe"},
- "sync/atomic": {"unsafe"},
- "unsafe": {},
- "internal/cpu": {},
- "internal/bytealg": {"unsafe", "internal/cpu"},
- "internal/reflectlite": {"runtime", "unsafe", "internal/unsafeheader"},
- "internal/unsafeheader": {"unsafe"},
-
- "L0": {
- "errors",
- "io",
- "runtime",
- "runtime/internal/atomic",
- "sync",
- "sync/atomic",
- "unsafe",
- "internal/cpu",
- "internal/bytealg",
- "internal/reflectlite",
- },
-
- // L1 adds simple functions and strings processing,
- // but not Unicode tables.
- "math": {"internal/cpu", "unsafe", "math/bits"},
- "math/bits": {"unsafe"},
- "math/cmplx": {"math", "math/bits"},
- "math/rand": {"L0", "math"},
- "strconv": {"L0", "unicode/utf8", "math", "math/bits"},
- "unicode/utf16": {},
- "unicode/utf8": {},
-
- "L1": {
- "L0",
- "math",
- "math/bits",
- "math/cmplx",
- "math/rand",
- "sort",
- "strconv",
- "unicode/utf16",
- "unicode/utf8",
- },
-
- // L2 adds Unicode and strings processing.
- "bufio": {"L0", "unicode/utf8", "bytes", "strings"},
- "bytes": {"L0", "unicode", "unicode/utf8"},
- "path": {"L0", "unicode/utf8", "strings"},
- "strings": {"L0", "unicode", "unicode/utf8"},
- "unicode": {},
-
- "L2": {
- "L1",
- "bufio",
- "bytes",
- "path",
- "strings",
- "unicode",
- },
-
- // L3 adds reflection and some basic utility packages
- // and interface definitions, but nothing that makes
- // system calls.
- "crypto": {"L2", "hash"}, // interfaces
- "crypto/cipher": {"L2", "crypto/subtle", "crypto/internal/subtle", "encoding/binary"},
- "crypto/internal/subtle": {"unsafe", "reflect"}, // reflect behind a appengine tag
- "crypto/subtle": {},
- "encoding/base32": {"L2"},
- "encoding/base64": {"L2", "encoding/binary"},
- "encoding/binary": {"L2", "reflect"},
- "hash": {"L2"}, // interfaces
- "hash/adler32": {"L2", "hash"},
- "hash/crc32": {"L2", "hash"},
- "hash/crc64": {"L2", "hash"},
- "hash/fnv": {"L2", "hash"},
- "hash/maphash": {"L2", "hash"},
- "image": {"L2", "image/color"}, // interfaces
- "image/color": {"L2"}, // interfaces
- "image/color/palette": {"L2", "image/color"},
- "internal/fmtsort": {"reflect", "sort"},
- "reflect": {"L2", "internal/unsafeheader"},
- "sort": {"internal/reflectlite"},
-
- "crypto/internal/boring": {"L2", "C", "crypto", "crypto/cipher", "crypto/internal/boring/sig", "crypto/subtle", "encoding/asn1", "hash", "math/big"},
- "crypto/internal/boring/fipstls": {"sync/atomic"},
- "crypto/internal/cipherhw": {"crypto/internal/boring"},
- "crypto/tls/fipsonly": {"crypto/internal/boring/fipstls", "crypto/internal/boring/sig"},
-
- "L3": {
- "L2",
- "crypto",
- "crypto/cipher",
- "crypto/internal/boring",
- "crypto/internal/boring/fipstls",
- "crypto/internal/subtle",
- "crypto/subtle",
- "encoding/base32",
- "encoding/base64",
- "encoding/binary",
- "hash",
- "hash/adler32",
- "hash/crc32",
- "hash/crc64",
- "hash/fnv",
- "image",
- "image/color",
- "image/color/palette",
- "internal/fmtsort",
- "internal/oserror",
- "reflect",
- },
-
- // End of linear dependency definitions.
-
- // Operating system access.
- "syscall": {"L0", "internal/oserror", "internal/race", "internal/syscall/windows/sysdll", "internal/unsafeheader", "syscall/js", "unicode/utf16"},
- "syscall/js": {"L0"},
- "internal/oserror": {"L0"},
- "internal/syscall/unix": {"L0", "syscall"},
- "internal/syscall/windows": {"L0", "syscall", "internal/syscall/windows/sysdll", "internal/unsafeheader", "unicode/utf16"},
- "internal/syscall/windows/registry": {"L0", "syscall", "internal/syscall/windows/sysdll", "unicode/utf16"},
- "internal/syscall/execenv": {"L0", "syscall", "internal/syscall/windows", "unicode/utf16"},
- "time": {
- // "L0" without the "io" package:
- "errors",
- "runtime",
- "runtime/internal/atomic",
- "sync",
- "sync/atomic",
- "unsafe",
- // Other time dependencies:
- "internal/syscall/windows/registry",
- "syscall",
- "syscall/js",
- "time/tzdata",
- },
- "time/tzdata": {"L0", "syscall"},
-
- "internal/cfg": {"L0"},
- "internal/poll": {"L0", "internal/oserror", "internal/race", "syscall", "time", "unicode/utf16", "unicode/utf8", "internal/syscall/windows", "internal/syscall/unix"},
- "internal/testlog": {"L0"},
- "os": {"L1", "os", "syscall", "time", "internal/oserror", "internal/poll", "internal/syscall/windows", "internal/syscall/unix", "internal/syscall/execenv", "internal/testlog"},
- "path/filepath": {"L2", "os", "syscall", "internal/syscall/windows"},
- "io/ioutil": {"L2", "os", "path/filepath", "time"},
- "os/exec": {"L2", "os", "context", "path/filepath", "syscall", "internal/syscall/execenv"},
- "os/signal": {"L2", "os", "syscall"},
-
- // OS enables basic operating system functionality,
- // but not direct use of package syscall, nor os/signal.
- "OS": {
- "io/ioutil",
- "os",
- "os/exec",
- "path/filepath",
- "time",
- },
-
- // Formatted I/O: few dependencies (L1) but we must add reflect and internal/fmtsort.
- "fmt": {"L1", "os", "reflect", "internal/fmtsort"},
- "log": {"L1", "os", "fmt", "time"},
-
- // Packages used by testing must be low-level (L2+fmt).
- "regexp": {"L2", "regexp/syntax"},
- "regexp/syntax": {"L2"},
- "runtime/debug": {"L2", "fmt", "io/ioutil", "os", "time"},
- "runtime/pprof": {"L2", "compress/gzip", "context", "encoding/binary", "fmt", "io/ioutil", "os", "syscall", "text/tabwriter", "time"},
- "runtime/trace": {"L0", "context", "fmt"},
- "text/tabwriter": {"L2"},
-
- "testing": {"L2", "flag", "fmt", "internal/race", "io/ioutil", "os", "runtime/debug", "runtime/pprof", "runtime/trace", "time"},
- "testing/iotest": {"L2", "log"},
- "testing/quick": {"L2", "flag", "fmt", "reflect", "time"},
- "internal/obscuretestdata": {"L2", "OS", "encoding/base64"},
- "internal/testenv": {"L2", "OS", "flag", "testing", "syscall", "internal/cfg"},
- "internal/lazyregexp": {"L2", "OS", "regexp"},
- "internal/lazytemplate": {"L2", "OS", "text/template"},
-
- // L4 is defined as L3+fmt+log+time, because in general once
- // you're using L3 packages, use of fmt, log, or time is not a big deal.
- "L4": {
- "L3",
- "fmt",
- "log",
- "time",
- },
-
- // Go parser.
- "go/ast": {"L4", "OS", "go/scanner", "go/token"},
- "go/doc": {"L4", "OS", "go/ast", "go/token", "regexp", "internal/lazyregexp", "text/template"},
- "go/parser": {"L4", "OS", "go/ast", "go/scanner", "go/token"},
- "go/printer": {"L4", "OS", "go/ast", "go/scanner", "go/token", "text/tabwriter"},
- "go/scanner": {"L4", "OS", "go/token"},
- "go/token": {"L4"},
-
- "GOPARSER": {
- "go/ast",
- "go/doc",
- "go/parser",
- "go/printer",
- "go/scanner",
- "go/token",
- },
-
- "go/format": {"L4", "GOPARSER", "internal/format"},
- "internal/format": {"L4", "GOPARSER"},
-
- // Go type checking.
- "go/constant": {"L4", "go/token", "math/big"},
- "go/importer": {"L4", "go/build", "go/internal/gccgoimporter", "go/internal/gcimporter", "go/internal/srcimporter", "go/token", "go/types"},
- "go/internal/gcimporter": {"L4", "OS", "go/build", "go/constant", "go/token", "go/types", "text/scanner"},
- "go/internal/gccgoimporter": {"L4", "OS", "debug/elf", "go/constant", "go/token", "go/types", "internal/xcoff", "text/scanner"},
- "go/internal/srcimporter": {"L4", "OS", "fmt", "go/ast", "go/build", "go/parser", "go/token", "go/types", "path/filepath"},
- "go/types": {"L4", "GOPARSER", "container/heap", "go/constant"},
-
- // One of a kind.
- "archive/tar": {"L4", "OS", "syscall", "os/user"},
- "archive/zip": {"L4", "OS", "compress/flate"},
- "container/heap": {"sort"},
- "compress/bzip2": {"L4"},
- "compress/flate": {"L4"},
- "compress/gzip": {"L4", "compress/flate"},
- "compress/lzw": {"L4"},
- "compress/zlib": {"L4", "compress/flate"},
- "context": {"errors", "internal/reflectlite", "sync", "sync/atomic", "time"},
- "database/sql": {"L4", "container/list", "context", "database/sql/driver", "database/sql/internal"},
- "database/sql/driver": {"L4", "context", "time", "database/sql/internal"},
- "debug/dwarf": {"L4"},
- "debug/elf": {"L4", "OS", "debug/dwarf", "compress/zlib"},
- "debug/gosym": {"L4"},
- "debug/macho": {"L4", "OS", "debug/dwarf", "compress/zlib"},
- "debug/pe": {"L4", "OS", "debug/dwarf", "compress/zlib"},
- "debug/plan9obj": {"L4", "OS"},
- "encoding": {"L4"},
- "encoding/ascii85": {"L4"},
- "encoding/asn1": {"L4", "math/big"},
- "encoding/csv": {"L4"},
- "encoding/gob": {"L4", "OS", "encoding"},
- "encoding/hex": {"L4"},
- "encoding/json": {"L4", "encoding"},
- "encoding/pem": {"L4"},
- "encoding/xml": {"L4", "encoding"},
- "flag": {"L4", "OS"},
- "go/build": {"L4", "OS", "GOPARSER", "internal/goroot", "internal/goversion"},
- "html": {"L4"},
- "image/draw": {"L4", "image/internal/imageutil"},
- "image/gif": {"L4", "compress/lzw", "image/color/palette", "image/draw"},
- "image/internal/imageutil": {"L4"},
- "image/jpeg": {"L4", "image/internal/imageutil"},
- "image/png": {"L4", "compress/zlib"},
- "index/suffixarray": {"L4", "regexp"},
- "internal/goroot": {"L4", "OS"},
- "internal/singleflight": {"sync"},
- "internal/trace": {"L4", "OS", "container/heap"},
- "internal/xcoff": {"L4", "OS", "debug/dwarf"},
- "math/big": {"L4"},
- "mime": {"L4", "OS", "syscall", "internal/syscall/windows/registry"},
- "mime/quotedprintable": {"L4"},
- "net/internal/socktest": {"L4", "OS", "syscall", "internal/syscall/windows"},
- "net/url": {"L4"},
- "plugin": {"L0", "OS", "CGO"},
- "internal/profile": {"L4", "OS", "compress/gzip", "regexp"},
- "testing/internal/testdeps": {"L4", "internal/testlog", "runtime/pprof", "regexp"},
- "text/scanner": {"L4", "OS"},
- "text/template/parse": {"L4"},
-
- "html/template": {
- "L4", "OS", "encoding/json", "html", "text/template",
- "text/template/parse",
- },
- "text/template": {
- "L4", "OS", "net/url", "text/template/parse",
- },
-
- // Cgo.
- // If you add a dependency on CGO, you must add the package to
- // cgoPackages in cmd/dist/test.go.
- "runtime/cgo": {"L0", "C"},
- "CGO": {"C", "runtime/cgo"},
-
- // Fake entry to satisfy the pseudo-import "C"
- // that shows up in programs that use cgo.
- "C": {},
-
- // Race detector/MSan uses cgo.
- "runtime/race": {"C"},
- "runtime/msan": {"C"},
-
- // Plan 9 alone needs io/ioutil and os.
- "os/user": {"L4", "CGO", "io/ioutil", "os", "syscall", "internal/syscall/windows", "internal/syscall/windows/registry"},
-
- // Internal package used only for testing.
- "os/signal/internal/pty": {"CGO", "fmt", "os", "syscall"},
-
- // Basic networking.
- // Because net must be used by any package that wants to
- // do networking portably, it must have a small dependency set: just L0+basic os.
- "net": {
- "L0", "CGO",
- "context", "math/rand", "os", "sort", "syscall", "time",
- "internal/nettrace", "internal/poll", "internal/syscall/unix",
- "internal/syscall/windows", "internal/singleflight", "internal/race",
- "golang.org/x/net/dns/dnsmessage", "golang.org/x/net/lif", "golang.org/x/net/route",
- },
-
- // NET enables use of basic network-related packages.
- "NET": {
- "net",
- "mime",
- "net/textproto",
- "net/url",
- },
-
- // Uses of networking.
- "log/syslog": {"L4", "OS", "net"},
- "net/mail": {"L4", "NET", "OS", "mime"},
- "net/textproto": {"L4", "OS", "net"},
-
- // Core crypto.
- "crypto/aes": {"L3"},
- "crypto/des": {"L3"},
- "crypto/hmac": {"L3"},
- "crypto/internal/randutil": {"io", "sync"},
- "crypto/md5": {"L3"},
- "crypto/rc4": {"L3"},
- "crypto/sha1": {"L3"},
- "crypto/sha256": {"L3"},
- "crypto/sha512": {"L3"},
-
- "CRYPTO": {
- "crypto/aes",
- "crypto/des",
- "crypto/hmac",
- "crypto/internal/randutil",
- "crypto/md5",
- "crypto/rc4",
- "crypto/sha1",
- "crypto/sha256",
- "crypto/sha512",
- "golang.org/x/crypto/chacha20poly1305",
- "golang.org/x/crypto/curve25519",
- "golang.org/x/crypto/poly1305",
- },
-
- // Random byte, number generation.
- // This would be part of core crypto except that it imports
- // math/big, which imports fmt.
- "crypto/rand": {"L4", "CRYPTO", "OS", "math/big", "syscall", "syscall/js", "internal/syscall/unix"},
-
- // Not part of CRYPTO because it imports crypto/rand and crypto/sha512.
- "crypto/ed25519": {"L3", "CRYPTO", "crypto/rand", "crypto/ed25519/internal/edwards25519"},
- "crypto/ed25519/internal/edwards25519": {"encoding/binary"},
-
- // Mathematical crypto: dependencies on fmt (L4) and math/big.
- // We could avoid some of the fmt, but math/big imports fmt anyway.
- "crypto/dsa": {"L4", "CRYPTO", "math/big"},
- "crypto/ecdsa": {
- "L4", "CRYPTO", "crypto/elliptic", "math/big",
- "golang.org/x/crypto/cryptobyte", "golang.org/x/crypto/cryptobyte/asn1",
- },
- "crypto/elliptic": {"L4", "CRYPTO", "math/big"},
- "crypto/rsa": {"L4", "CRYPTO", "crypto/rand", "math/big"},
-
- "CRYPTO-MATH": {
- "CRYPTO",
- "crypto/dsa",
- "crypto/ecdsa",
- "crypto/elliptic",
- "crypto/rand",
- "crypto/rsa",
- "encoding/asn1",
- "math/big",
- },
-
- // SSL/TLS.
- "crypto/tls": {
- "L4", "CRYPTO-MATH", "OS", "golang.org/x/crypto/cryptobyte", "golang.org/x/crypto/hkdf",
- "container/list", "context", "crypto/x509", "encoding/pem", "net", "syscall", "crypto/ed25519",
- },
- "crypto/x509": {
- "L4", "CRYPTO-MATH", "OS", "CGO", "crypto/ed25519",
- "crypto/x509/pkix", "encoding/pem", "encoding/hex", "net", "os/user", "syscall", "net/url",
- "golang.org/x/crypto/cryptobyte", "golang.org/x/crypto/cryptobyte/asn1",
- },
- "crypto/x509/pkix": {"L4", "CRYPTO-MATH", "encoding/hex"},
-
- // Simple net+crypto-aware packages.
- "mime/multipart": {"L4", "OS", "mime", "crypto/rand", "net/textproto", "mime/quotedprintable"},
- "net/smtp": {"L4", "CRYPTO", "NET", "crypto/tls"},
-
- // HTTP, kingpin of dependencies.
- "net/http": {
- "L4", "NET", "OS",
- "compress/gzip",
- "container/list",
- "context",
- "crypto/rand",
- "crypto/tls",
- "golang.org/x/net/http/httpguts",
- "golang.org/x/net/http/httpproxy",
- "golang.org/x/net/http2/hpack",
- "golang.org/x/net/idna",
- "golang.org/x/text/unicode/norm",
- "golang.org/x/text/width",
- "internal/nettrace",
- "mime/multipart",
- "net/http/httptrace",
- "net/http/internal",
- "runtime/debug",
- "syscall/js",
- },
- "net/http/internal": {"L4"},
- "net/http/httptrace": {"context", "crypto/tls", "internal/nettrace", "net", "net/textproto", "reflect", "time"},
-
- // HTTP-using packages.
- "expvar": {"L4", "OS", "encoding/json", "net/http"},
- "net/http/cgi": {"L4", "NET", "OS", "crypto/tls", "net/http", "regexp"},
- "net/http/cookiejar": {"L4", "NET", "net/http"},
- "net/http/fcgi": {"L4", "NET", "OS", "context", "net/http", "net/http/cgi"},
- "net/http/httptest": {
- "L4", "NET", "OS", "crypto/tls", "flag", "net/http", "net/http/internal", "crypto/x509",
- "golang.org/x/net/http/httpguts",
- },
- "net/http/httputil": {"L4", "NET", "OS", "context", "net/http", "net/http/internal", "golang.org/x/net/http/httpguts"},
- "net/http/pprof": {"L4", "OS", "context", "html/template", "net/http", "runtime/pprof", "runtime/trace", "internal/profile"},
- "net/rpc": {"L4", "NET", "encoding/gob", "html/template", "net/http", "go/token"},
- "net/rpc/jsonrpc": {"L4", "NET", "encoding/json", "net/rpc"},
- }
+ // The general syntax of a rule is:
+ //
+ // a, b < c, d;
+ //
+ // which means c and d come after a and b in the partial order
+ // (that is, c and d can import a and b),
+ // but doesn't provide a relative order between a vs b or c vs d.
+ //
+ // The rules can chain together, as in:
+ //
+ // e < f, g < h;
+ //
+ // which is equivalent to
+ //
+ // e < f, g;
+ // f, g < h;
+ //
+ // Except for the special bottom element "NONE", each name
+ // must appear exactly once on the right-hand side of a rule.
+ // That rule serves as the definition of the allowed dependencies
+ // for that name. The definition must appear before any uses
+ // of the name on the left-hand side of a rule. (That is, the
+ // rules themselves must be ordered according to the partial
+ // order, for easier reading by people.)
+ //
+ // Negative assertions double-check the partial order:
+ //
+ // i !< j
+ //
+ // means that it must NOT be the case that i < j.
+ // Negative assertions may appear anywhere in the rules,
+ // even before i and j have been defined.
+ //
+ // Comments begin with #.
+ //
+ // All-caps names are pseudo-names for specific points
+ // in the dependency lattice.
+ //
+ var depsRules = `
+ # No dependencies allowed for any of these packages.
+ NONE
+ < container/list, container/ring,
+ internal/cfg, internal/cpu,
+ internal/goversion, internal/nettrace,
+ unicode/utf8, unicode/utf16, unicode,
+ unsafe;
+
+ # RUNTIME is the core runtime group of packages, all of them very light-weight.
+ internal/cpu, unsafe
+ < internal/bytealg
+ < internal/unsafeheader
+ < runtime/internal/sys
+ < runtime/internal/atomic
+ < runtime/internal/math
+ < runtime
+ < sync/atomic
+ < internal/race
+ < sync
+ < internal/reflectlite
+ < errors
+ < internal/oserror, math/bits
+ < RUNTIME;
+
+ RUNTIME
+ < sort
+ < container/heap;
+
+ RUNTIME
+ < io;
+
+ reflect !< sort;
+
+ # SYSCALL is RUNTIME plus the packages necessary for basic system calls.
+ RUNTIME, unicode/utf8, unicode/utf16, io
+ < internal/syscall/windows/sysdll, syscall/js
+ < syscall
+ < internal/syscall/unix, internal/syscall/windows, internal/syscall/windows/registry
+ < internal/syscall/execenv
+ < SYSCALL;
+
+ # TIME is SYSCALL plus the core packages about time, including context.
+ SYSCALL
+ < time/tzdata
+ < time
+ < context
+ < TIME;
+
+ # MATH is RUNTIME plus the basic math packages.
+ RUNTIME
+ < math
+ < MATH;
+
+ unicode !< math;
+
+ MATH
+ < math/cmplx;
+
+ MATH
+ < math/rand;
+
+ MATH, unicode/utf8
+ < strconv;
+
+ unicode !< strconv;
+
+ # STR is basic string and buffer manipulation.
+ RUNTIME, io, unicode/utf8, unicode/utf16, unicode
+ < bytes, strings
+ < bufio, path;
+
+ bufio, path, strconv
+ < STR;
+
+ # OS is basic OS access, including helpers (path/filepath, os/exec, etc).
+ # OS includes string routines, but those must be layered above package os.
+ # OS does not include reflection.
+ TIME, io, sort
+ < internal/testlog
+ < internal/poll
+ < os
+ < os/signal;
+
+ unicode, fmt !< os, os/signal;
+
+ os/signal, STR
+ < path/filepath
+ < io/ioutil, os/exec
+ < OS;
+
+ reflect !< OS;
+
+ OS
+ < golang.org/x/sys/cpu, internal/goroot;
+
+ # FMT is OS (which includes string routines) plus reflect and fmt.
+ # It does not include package log, which should be avoided in core packages.
+ strconv, unicode
+ < reflect;
+
+ os, reflect
+ < internal/fmtsort
+ < fmt;
+
+ OS, fmt
+ < FMT;
+
+ log !< FMT;
+
+ # Misc packages needing only FMT.
+ FMT
+ < flag,
+ html,
+ mime/quotedprintable,
+ net/internal/socktest,
+ net/url,
+ runtime/debug,
+ runtime/trace,
+ text/scanner,
+ text/tabwriter;
+
+ # encodings
+ # core ones do not use fmt.
+ io, strconv
+ < encoding;
+
+ encoding, reflect
+ < encoding/binary
+ < encoding/base32, encoding/base64;
+
+ fmt !< encoding/base32, encoding/base64;
+
+ FMT, encoding/base32, encoding/base64
+ < encoding/ascii85, encoding/csv, encoding/gob, encoding/hex,
+ encoding/json, encoding/pem, encoding/xml, mime;
+
+ # hashes
+ io
+ < hash
+ < hash/adler32, hash/crc32, hash/crc64, hash/fnv, hash/maphash;
+
+ # math/big
+ FMT, encoding/binary, math/rand
+ < math/big;
+
+ # compression
+ FMT, encoding/binary, hash/adler32, hash/crc32
+ < compress/bzip2, compress/flate, compress/lzw
+ < archive/zip, compress/gzip, compress/zlib;
+
+ # templates
+ FMT
+ < text/template/parse;
+
+ net/url, text/template/parse
+ < text/template
+ < internal/lazytemplate;
+
+ encoding/json, html, text/template
+ < html/template;
+
+ # regexp
+ FMT
+ < regexp/syntax
+ < regexp
+ < internal/lazyregexp;
+
+ # suffix array
+ encoding/binary, regexp
+ < index/suffixarray;
+
+ # executable parsing
+ FMT, encoding/binary, compress/zlib
+ < debug/dwarf
+ < debug/elf, debug/gosym, debug/macho, debug/pe, debug/plan9obj, internal/xcoff
+ < DEBUG;
+
+ # go parser and friends.
+ FMT
+ < go/token
+ < go/scanner
+ < go/ast
+ < go/parser;
+
+ go/parser, text/tabwriter
+ < go/printer
+ < go/format;
+
+ go/parser, internal/lazyregexp, text/template
+ < go/doc;
+
+ math/big, go/token
+ < go/constant;
+
+ container/heap, go/constant, go/parser
+ < go/types;
+
+ go/doc, go/parser, internal/goroot, internal/goversion
+ < go/build;
+
+ DEBUG, go/build, go/types, text/scanner
+ < go/internal/gcimporter, go/internal/gccgoimporter, go/internal/srcimporter
+ < go/importer;
+
+ # databases
+ FMT
+ < database/sql/internal
+ < database/sql/driver
+ < database/sql;
+
+ # images
+ FMT, compress/lzw, compress/zlib
+ < image/color
+ < image, image/color/palette
+ < image/internal/imageutil
+ < image/draw
+ < image/gif, image/jpeg, image/png;
+
+ # cgo, delayed as long as possible.
+ # If you add a dependency on CGO, you must add the package
+ # to cgoPackages in cmd/dist/test.go as well.
+ RUNTIME
+ < C
+ < runtime/cgo
+ < CGO
+ < runtime/race, runtime/msan;
+
+ # Bulk of the standard library must not use cgo.
+ # The prohibition stops at net and os/user.
- C !< fmt, go/types, CRYPTO-MATH;
++ C !< fmt, go/types;
+
+ CGO, OS
+ < plugin;
+
+ CGO, FMT
+ < os/user
+ < archive/tar;
+
+ sync
+ < internal/singleflight;
+
+ os
+ < golang.org/x/net/dns/dnsmessage,
+ golang.org/x/net/lif,
+ golang.org/x/net/route;
+
+ # net is unavoidable when doing any networking,
+ # so large dependencies must be kept out.
+ # This is a long-looking list but most of these
+ # are small with few dependencies.
+ # math/rand should probably be removed at some point.
+ CGO,
+ golang.org/x/net/dns/dnsmessage,
+ golang.org/x/net/lif,
+ golang.org/x/net/route,
+ internal/nettrace,
+ internal/poll,
+ internal/singleflight,
+ internal/race,
+ math/rand,
+ os
+ < net;
+
+ fmt, unicode !< net;
+
+ # NET is net plus net-helper packages.
+ FMT, net
+ < net/textproto;
+
+ mime, net/textproto, net/url
+ < NET;
+
+ # logging - most packages should not import; http and up is allowed
+ FMT
+ < log;
+
+ log !< crypto/tls, database/sql, go/importer, testing;
+
+ FMT, log, net
+ < log/syslog;
+
+ NET, log
+ < net/mail;
+
- # CRYPTO is core crypto algorithms - no cgo, fmt, net.
- # Unfortunately, stuck with reflect via encoding/binary.
- encoding/binary, golang.org/x/sys/cpu, hash
++ NONE < crypto/internal/boring/sig;
++ sync/atomic < crypto/internal/boring/fipstls;
++
++ encoding/binary, golang.org/x/sys/cpu, hash,
++ FMT, math/big,
++ CGO, crypto/internal/boring/sig, crypto/internal/boring/fipstls
+ < crypto
+ < crypto/subtle
+ < crypto/internal/subtle
+ < crypto/cipher
++ < encoding/asn1
++ < crypto/internal/boring
+ < crypto/aes, crypto/des, crypto/hmac, crypto/md5, crypto/rc4,
+ crypto/sha1, crypto/sha256, crypto/sha512
- < CRYPTO;
-
- CGO, fmt, net !< CRYPTO;
-
- # CRYPTO-MATH is core bignum-based crypto - no cgo, net; fmt now ok.
- CRYPTO, FMT, math/big
+ < crypto/rand
+ < crypto/internal/randutil
+ < crypto/ed25519/internal/edwards25519
+ < crypto/ed25519
- < encoding/asn1
+ < golang.org/x/crypto/cryptobyte/asn1
+ < golang.org/x/crypto/cryptobyte
+ < golang.org/x/crypto/curve25519
+ < crypto/dsa, crypto/elliptic, crypto/rsa
+ < crypto/ecdsa
- < CRYPTO-MATH;
++ < CRYPTO-BORING;
+
- CGO, net !< CRYPTO-MATH;
++ net !< CRYPTO-BORING;
+
+ # TLS, Prince of Dependencies.
- CGO, CRYPTO-MATH, NET, container/list, encoding/hex, encoding/pem
++ CGO, CRYPTO-BORING, NET, container/list, encoding/hex, encoding/pem
+ < golang.org/x/crypto/internal/subtle
+ < golang.org/x/crypto/chacha20
+ < golang.org/x/crypto/poly1305
+ < golang.org/x/crypto/chacha20poly1305
+ < golang.org/x/crypto/hkdf
+ < crypto/x509/internal/macOS
+ < crypto/x509/pkix
+ < crypto/x509
+ < crypto/tls;
+
++ crypto/internal/boring/sig, crypto/internal/boring/fipstls
++ < crypto/tls/fipsonly;
++
+ # crypto-aware packages
+
+ NET, crypto/rand, mime/quotedprintable
+ < mime/multipart;
+
+ crypto/tls
+ < net/smtp;
+
+ # HTTP, King of Dependencies.
+
+ FMT
+ < golang.org/x/net/http2/hpack, net/http/internal;
+
+ FMT, NET, container/list, encoding/binary, log
+ < golang.org/x/text/transform
+ < golang.org/x/text/unicode/norm
+ < golang.org/x/text/unicode/bidi
+ < golang.org/x/text/secure/bidirule
+ < golang.org/x/net/idna
+ < golang.org/x/net/http/httpguts, golang.org/x/net/http/httpproxy;
+
+ NET, crypto/tls
+ < net/http/httptrace;
+
+ compress/gzip,
+ golang.org/x/net/http/httpguts,
+ golang.org/x/net/http/httpproxy,
+ golang.org/x/net/http2/hpack,
+ net/http/internal,
+ net/http/httptrace,
+ mime/multipart,
+ log
+ < net/http;
+
+ # HTTP-aware packages
- // isMacro reports whether p is a package dependency macro
- // (uppercase name).
- func isMacro(p string) bool {
- return 'A' <= p[0] && p[0] <= 'Z'
- }
+ encoding/json, net/http
+ < expvar;
- func allowed(pkg string) map[string]bool {
- m := map[string]bool{}
- var allow func(string)
- allow = func(p string) {
- if m[p] {
- return
- }
- m[p] = true // set even for macros, to avoid loop on cycle
+ net/http
+ < net/http/cookiejar, net/http/httputil;
+
+ net/http, flag
+ < net/http/httptest;
+
+ net/http, regexp
+ < net/http/cgi
+ < net/http/fcgi;
+
+ # Profiling
+ FMT, compress/gzip, encoding/binary, text/tabwriter
+ < runtime/pprof;
+
+ OS, compress/gzip, regexp
+ < internal/profile;
+
+ html/template, internal/profile, net/http, runtime/pprof, runtime/trace
+ < net/http/pprof;
+
+ # RPC
+ encoding/gob, encoding/json, go/token, html/template, net/http
+ < net/rpc
+ < net/rpc/jsonrpc;
+
+ # Test-only
+ log
+ < testing/iotest;
+
+ FMT, flag, math/rand
+ < testing/quick;
- // Upper-case names are macro-expanded.
- if isMacro(p) {
- for _, pp := range pkgDeps[p] {
- allow(pp)
- }
- }
- }
- for _, pp := range pkgDeps[pkg] {
- allow(pp)
- }
- return m
- }
+ FMT, flag, runtime/debug, runtime/trace
+ < testing;
+
+ internal/testlog, runtime/pprof, regexp
+ < testing/internal/testdeps;
+
+ OS, flag, testing, internal/cfg
+ < internal/testenv;
+
+ OS, encoding/base64
+ < internal/obscuretestdata;
+
+ CGO, OS, fmt
+ < os/signal/internal/pty;
+
+ NET, testing
+ < golang.org/x/net/nettest;
+
+ FMT, container/heap, math/rand
+ < internal/trace;
+ `
// listStdPkgs returns the same list of packages as "go list std".
func listStdPkgs(goroot string) ([]string, error) {
}
name := filepath.ToSlash(path[len(src):])
- if name == "builtin" || name == "cmd" || strings.Contains(name, "golang.org/x/") {
+ if name == "builtin" || name == "cmd" {
return filepath.SkipDir
}
- pkgs = append(pkgs, name)
+ pkgs = append(pkgs, strings.TrimPrefix(name, "vendor/"))
return nil
}
if err := filepath.Walk(src, walkFn); err != nil {
sort.Strings(all)
sawImport := map[string]map[string]bool{} // from package => to package => true
+ policy := depsPolicy(t)
for _, pkg := range all {
imports, err := findImports(pkg)
if sawImport[pkg] == nil {
sawImport[pkg] = map[string]bool{}
}
- ok := allowed(pkg)
+ ok := policy[pkg]
var bad []string
for _, imp := range imports {
sawImport[pkg][imp] = true
}
return ""
}
-
- // Also test some high-level policy goals are being met by not finding
- // these dependency paths:
- badPaths := []struct{ from, to string }{
- {"net", "unicode"},
- {"os", "unicode"},
- }
-
- for _, path := range badPaths {
- if how := depPath(path.from, path.to); how != "" {
- t.Errorf("policy violation: %s", how)
- }
- }
-
}
var buildIgnore = []byte("\n// +build ignore")
func findImports(pkg string) ([]string, error) {
- dir := filepath.Join(Default.GOROOT, "src", pkg)
+ vpkg := pkg
+ if strings.HasPrefix(pkg, "golang.org") {
+ vpkg = "vendor/" + pkg
+ }
+ dir := filepath.Join(Default.GOROOT, "src", vpkg)
files, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
sort.Strings(imports)
return imports, nil
}
+
+ // depsPolicy returns a map m such that m[p][d] == true when p can import d.
+ func depsPolicy(t *testing.T) map[string]map[string]bool {
+ allowed := map[string]map[string]bool{"NONE": {}}
+ disallowed := [][2][]string{}
+
+ parseDepsRules(t, func(deps []string, op string, users []string) {
+ if op == "!<" {
+ disallowed = append(disallowed, [2][]string{deps, users})
+ return
+ }
+ for _, u := range users {
+ if allowed[u] != nil {
+ t.Errorf("multiple deps lists for %s", u)
+ }
+ allowed[u] = make(map[string]bool)
+ for _, d := range deps {
+ if allowed[d] == nil {
+ t.Errorf("use of %s before its deps list", d)
+ }
+ allowed[u][d] = true
+ }
+ }
+ })
+
+ // Check for missing deps info.
+ for _, deps := range allowed {
+ for d := range deps {
+ if allowed[d] == nil {
+ t.Errorf("missing deps list for %s", d)
+ }
+ }
+ }
+
+ // Complete transitive allowed deps.
+ for k := range allowed {
+ for i := range allowed {
+ for j := range allowed {
+ if i != k && k != j && allowed[i][k] && allowed[k][j] {
+ if i == j {
+ // Can only happen along with a "use of X before deps" error above,
+ // but this error is more specific - it makes clear that reordering the
+ // rules will not be enough to fix the problem.
+ t.Errorf("deps policy cycle: %s < %s < %s", j, k, i)
+ }
+ allowed[i][j] = true
+ }
+ }
+ }
+ }
+
+ // Check negative assertions against completed allowed deps.
+ for _, bad := range disallowed {
+ deps, users := bad[0], bad[1]
+ for _, d := range deps {
+ for _, u := range users {
+ if allowed[u][d] {
+ t.Errorf("deps policy incorrect: assertion failed: %s !< %s", d, u)
+ }
+ }
+ }
+ }
+
+ if t.Failed() {
+ t.FailNow()
+ }
+
+ return allowed
+ }
+
+ // parseDepsRules parses depsRules, calling save(deps, op, users)
+ // for each deps < users or deps !< users rule
+ // (op is "<" or "!<").
+ func parseDepsRules(t *testing.T, save func(deps []string, op string, users []string)) {
+ p := &depsParser{t: t, lineno: 1, text: depsRules}
+
+ var prev []string
+ var op string
+ for {
+ list, tok := p.nextList()
+ if tok == "" {
+ if prev == nil {
+ break
+ }
+ p.syntaxError("unexpected EOF")
+ }
+ if prev != nil {
+ save(prev, op, list)
+ }
+ prev = list
+ if tok == ";" {
+ prev = nil
+ op = ""
+ continue
+ }
+ if tok != "<" && tok != "!<" {
+ p.syntaxError("missing <")
+ }
+ op = tok
+ }
+ }
+
+ // A depsParser parses the depsRules syntax described above.
+ type depsParser struct {
+ t *testing.T
+ lineno int
+ lastWord string
+ text string
+ }
+
+ // syntaxError reports a parsing error.
+ func (p *depsParser) syntaxError(msg string) {
+ p.t.Fatalf("deps:%d: syntax error: %s near %s", p.lineno, msg, p.lastWord)
+ }
+
+ // nextList parses and returns a comma-separated list of names.
+ func (p *depsParser) nextList() (list []string, token string) {
+ for {
+ tok := p.nextToken()
+ switch tok {
+ case "":
+ if len(list) == 0 {
+ return nil, ""
+ }
+ fallthrough
+ case ",", "<", "!<", ";":
+ p.syntaxError("bad list syntax")
+ }
+ list = append(list, tok)
+
+ tok = p.nextToken()
+ if tok != "," {
+ return list, tok
+ }
+ }
+ }
+
+ // nextToken returns the next token in the deps rules,
+ // one of ";" "," "<" "!<" or a name.
+ func (p *depsParser) nextToken() string {
+ for {
+ if p.text == "" {
+ return ""
+ }
+ switch p.text[0] {
+ case ';', ',', '<':
+ t := p.text[:1]
+ p.text = p.text[1:]
+ return t
+
+ case '!':
+ if len(p.text) < 2 || p.text[1] != '<' {
+ p.syntaxError("unexpected token !")
+ }
+ p.text = p.text[2:]
+ return "!<"
+
+ case '#':
+ i := strings.Index(p.text, "\n")
+ if i < 0 {
+ i = len(p.text)
+ }
+ p.text = p.text[i:]
+ continue
+
+ case '\n':
+ p.lineno++
+ fallthrough
+ case ' ', '\t':
+ p.text = p.text[1:]
+ continue
+
+ default:
+ i := strings.IndexAny(p.text, "!;,<#\n \t")
+ if i < 0 {
+ i = len(p.text)
+ }
+ t := p.text[:i]
+ p.text = p.text[i:]
+ p.lastWord = t
+ return t
+ }
+ }
+ }