diff --git a/.golangci.yml b/.golangci.yml index 8c5ca0f..bee66f3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -242,6 +242,10 @@ linters: check-type-assertions: true exclude-functions: - (*git.wntrmute.dev/kyle/goutils/sbuf.Buffer).Write + - git.wntrmute.dev/kyle/goutils/lib.Warn + - git.wntrmute.dev/kyle/goutils/lib.Warnx + - git.wntrmute.dev/kyle/goutils/lib.Err + - git.wntrmute.dev/kyle/goutils/lib.Errx exhaustive: # Program elements to check for exhaustiveness. @@ -333,6 +337,12 @@ linters: # https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#no-unused-link - no-unused-link + gosec: + excludes: + - G104 # handled by errcheck + - G301 + - G306 + govet: # Enable all analyzers. # Default: false @@ -368,6 +378,12 @@ linters: - os.WriteFile - prometheus.ExponentialBuckets.* - prometheus.LinearBuckets + ignored-numbers: + - 1 + - 2 + - 3 + - 4 + - 8 nakedret: # Make an issue if func has more lines of code than this setting, and it has naked returns. @@ -436,6 +452,8 @@ linters: # Omit embedded fields from selector expression. # https://staticcheck.dev/docs/checks/#QF1008 - -QF1008 + # We often explicitly enable old/deprecated ciphers for research. + - -SA1019 usetesting: # Enable/disable `os.TempDir()` detections. diff --git a/certlib/helpers.go b/certlib/helpers.go index 749b7e6..9fc4b91 100644 --- a/certlib/helpers.go +++ b/certlib/helpers.go @@ -458,8 +458,6 @@ func GetKeyDERFromPEM(in []byte, password []byte) ([]byte, error) { } if procType, ok := keyDER.Headers["Proc-Type"]; ok && strings.Contains(procType, "ENCRYPTED") { if password != nil { - // nolintlint requires rationale: - //nolint:staticcheck // legacy RFC1423 PEM encryption supported for backward compatibility when caller supplies a password return x509.DecryptPEMBlock(keyDER, password) } return nil, certerr.DecodeError(certerr.ErrorSourcePrivateKey, certerr.ErrEncryptedPrivateKey) diff --git a/cmd/certchain/main.go b/cmd/certchain/main.go index 097b451..0df6460 100644 --- a/cmd/certchain/main.go +++ b/cmd/certchain/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "crypto/tls" "encoding/pem" "flag" @@ -22,22 +23,26 @@ func main() { server += ":443" } - var chain string - - conn, err := tls.Dial("tcp", server, nil) + d := &tls.Dialer{Config: &tls.Config{}} // #nosec G402 + nc, err := d.DialContext(context.Background(), "tcp", server) die.If(err) + conn, ok := nc.(*tls.Conn) + if !ok { + die.With("invalid TLS connection (not a *tls.Conn)") + } + + defer conn.Close() details := conn.ConnectionState() - var chainSb30 strings.Builder + var chain strings.Builder for _, cert := range details.PeerCertificates { p := pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, } - chainSb30.WriteString(string(pem.EncodeToMemory(&p))) + chain.Write(pem.EncodeToMemory(&p)) } - chain += chainSb30.String() - fmt.Fprintln(os.Stdout, chain) + fmt.Fprintln(os.Stdout, chain.String()) } } diff --git a/cmd/certdump/main.go b/cmd/certdump/main.go index 4c93371..2493a8b 100644 --- a/cmd/certdump/main.go +++ b/cmd/certdump/main.go @@ -3,6 +3,7 @@ package main import ( "bytes" + "context" "crypto/dsa" "crypto/ecdsa" "crypto/elliptic" @@ -246,18 +247,34 @@ func displayAllCerts(in []byte, leafOnly bool) { func displayAllCertsWeb(uri string, leafOnly bool) { ci := getConnInfo(uri) - conn, err := tls.Dial("tcp", ci.Addr, permissiveConfig()) + d := &tls.Dialer{Config: permissiveConfig()} + nc, err := d.DialContext(context.Background(), "tcp", ci.Addr) if err != nil { _, _ = lib.Warn(err, "couldn't connect to %s", ci.Addr) return } + + conn, ok := nc.(*tls.Conn) + if !ok { + _, _ = lib.Warnx("invalid TLS connection (not a *tls.Conn)") + return + } defer conn.Close() state := conn.ConnectionState() - conn.Close() + if err = conn.Close(); err != nil { + _, _ = lib.Warn(err, "couldn't close TLS connection") + } - conn, err = tls.Dial("tcp", ci.Addr, verifyConfig(ci.Host)) + d = &tls.Dialer{Config: verifyConfig(ci.Host)} + nc, err = d.DialContext(context.Background(), "tcp", ci.Addr) if err == nil { + conn, ok = nc.(*tls.Conn) + if !ok { + _, _ = lib.Warnx("invalid TLS connection (not a *tls.Conn)") + return + } + err = conn.VerifyHostname(ci.Host) if err == nil { state = conn.ConnectionState() @@ -293,6 +310,32 @@ func displayAllCertsWeb(uri string, leafOnly bool) { } } +func shouldReadStdin(argc int, argv []string) bool { + if argc == 0 { + return true + } + + if argc == 1 && argv[0] == "-" { + return true + } + + return false +} + +func readStdin(leafOnly bool) { + certs, err := io.ReadAll(os.Stdin) + if err != nil { + _, _ = lib.Warn(err, "couldn't read certificates from standard input") + os.Exit(1) + } + + // This is needed for getting certs from JSON/jq. + certs = bytes.TrimSpace(certs) + certs = bytes.ReplaceAll(certs, []byte(`\n`), []byte{0xa}) + certs = bytes.Trim(certs, `"`) + displayAllCerts(certs, leafOnly) +} + func main() { var leafOnly bool flag.BoolVar(&showHash, "d", false, "show hashes of raw DER contents") @@ -300,32 +343,23 @@ func main() { flag.BoolVar(&leafOnly, "l", false, "only show the leaf certificate") flag.Parse() - if flag.NArg() == 0 || (flag.NArg() == 1 && flag.Arg(0) == "-") { - certs, err := io.ReadAll(os.Stdin) - if err != nil { - _, _ = lib.Warn(err, "couldn't read certificates from standard input") - os.Exit(1) - } + if shouldReadStdin(flag.NArg(), flag.Args()) { + readStdin(leafOnly) + return + } - // This is needed for getting certs from JSON/jq. - certs = bytes.TrimSpace(certs) - certs = bytes.ReplaceAll(certs, []byte(`\n`), []byte{0xa}) - certs = bytes.Trim(certs, `"`) - displayAllCerts(certs, leafOnly) - } else { - for _, filename := range flag.Args() { - fmt.Fprintf(os.Stdout, "--%s ---%s", filename, "\n") - if strings.HasPrefix(filename, "https://") { - displayAllCertsWeb(filename, leafOnly) - } else { - in, err := os.ReadFile(filename) - if err != nil { - _, _ = lib.Warn(err, "couldn't read certificate") - continue - } - - displayAllCerts(in, leafOnly) + for _, filename := range flag.Args() { + fmt.Fprintf(os.Stdout, "--%s ---%s", filename, "\n") + if strings.HasPrefix(filename, "https://") { + displayAllCertsWeb(filename, leafOnly) + } else { + in, err := os.ReadFile(filename) + if err != nil { + _, _ = lib.Warn(err, "couldn't read certificate") + continue } + + displayAllCerts(in, leafOnly) } } } diff --git a/cmd/certdump/util.go b/cmd/certdump/util.go index 3b104d0..5bae1f8 100644 --- a/cmd/certdump/util.go +++ b/cmd/certdump/util.go @@ -13,6 +13,11 @@ import ( // following two lifted from CFSSL, (replace-regexp "\(.+\): \(.+\)," // "\2: \1,") +const ( + sSHA256 = "SHA256" + sSHA512 = "SHA512" +) + var keyUsage = map[x509.KeyUsage]string{ x509.KeyUsageDigitalSignature: "digital signature", x509.KeyUsageContentCommitment: "content committment", @@ -70,19 +75,19 @@ func sigAlgoHash(a x509.SignatureAlgorithm) string { case x509.SHA1WithRSA, x509.ECDSAWithSHA1, x509.DSAWithSHA1: return "SHA1" case x509.SHA256WithRSA, x509.ECDSAWithSHA256, x509.DSAWithSHA256: - return "SHA256" + return sSHA256 case x509.SHA256WithRSAPSS: - return "SHA256" + return sSHA256 case x509.SHA384WithRSA, x509.ECDSAWithSHA384: return "SHA384" case x509.SHA384WithRSAPSS: return "SHA384" case x509.SHA512WithRSA, x509.ECDSAWithSHA512: - return "SHA512" + return sSHA512 case x509.SHA512WithRSAPSS: - return "SHA512" + return sSHA512 case x509.PureEd25519: - return "SHA512" + return sSHA512 case x509.UnknownSignatureAlgorithm: return "unknown hash algorithm" default: @@ -144,14 +149,14 @@ func dumpHex(in []byte) string { func permissiveConfig() *tls.Config { return &tls.Config{ InsecureSkipVerify: true, - } + } // #nosec G402 } // verifyConfig returns a config that will verify the connection. func verifyConfig(hostname string) *tls.Config { return &tls.Config{ ServerName: hostname, - } + } // #nosec G402 } type connInfo struct { diff --git a/cmd/clustersh/main.go b/cmd/clustersh/main.go index 1f8abfd..dd745ef 100644 --- a/cmd/clustersh/main.go +++ b/cmd/clustersh/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "errors" "flag" "fmt" @@ -57,7 +58,7 @@ var modes = ssh.TerminalModes{ } func sshAgent() ssh.AuthMethod { - a, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")) + a, err := (&net.Dialer{}).DialContext(context.Background(), "unix", os.Getenv("SSH_AUTH_SOCK")) if err == nil { return ssh.PublicKeysCallback(agent.NewClient(a).Signers) } @@ -116,7 +117,7 @@ func exec(wg *sync.WaitGroup, user, host string, commands []string) { } shutdown = append(shutdown, session.Close) - if err := session.RequestPty("xterm", 80, 40, modes); err != nil { + if err = session.RequestPty("xterm", 80, 40, modes); err != nil { session.Close() logError(host, err, "request for pty failed") return diff --git a/cmd/cruntar/main.go b/cmd/cruntar/main.go index 9b43cd1..931476b 100644 --- a/cmd/cruntar/main.go +++ b/cmd/cruntar/main.go @@ -26,7 +26,7 @@ func setupFile(hdr *tar.Header, file *os.File) error { if verbose { fmt.Printf("\tchmod %0#o\n", hdr.Mode) } - err := file.Chmod(os.FileMode(hdr.Mode)) + err := file.Chmod(os.FileMode(hdr.Mode & 0xFFFFFFFF)) // #nosec G115 if err != nil { return err } @@ -55,7 +55,9 @@ func processFile(tfr *tar.Reader, hdr *tar.Header, top string) error { if verbose { fmt.Println(hdr.Name) } + filePath := filepath.Clean(filepath.Join(top, hdr.Name)) + switch hdr.Typeflag { case tar.TypeReg: file, err := os.Create(filePath) @@ -109,7 +111,7 @@ func processFile(tfr *tar.Reader, hdr *tar.Header, top string) error { return err } case tar.TypeDir: - err := os.MkdirAll(filePath, os.FileMode(hdr.Mode)) + err := os.MkdirAll(filePath, os.FileMode(hdr.Mode&0xFFFFFFFF)) // #nosec G115 if err != nil { return err } @@ -261,8 +263,9 @@ func main() { die.If(err) tfr := tar.NewReader(r) + var hdr *tar.Header for { - hdr, err := tfr.Next() + hdr, err = tfr.Next() if errors.Is(err, io.EOF) { break } diff --git a/cmd/csrpubdump/main.go b/cmd/csrpubdump/main.go index 7aca42b..a1cd44e 100644 --- a/cmd/csrpubdump/main.go +++ b/cmd/csrpubdump/main.go @@ -47,7 +47,7 @@ func main() { Bytes: out, } - err = os.WriteFile(fileName+".pub", pem.EncodeToMemory(p), 0o644) + err = os.WriteFile(fileName+".pub", pem.EncodeToMemory(p), 0o644) // #nosec G306 die.If(err) fmt.Fprintf(os.Stdout, "[+] wrote %s.\n", fileName+".pub") } diff --git a/cmd/data_sync/main.go b/cmd/data_sync/main.go index 016e3d0..8750c92 100644 --- a/cmd/data_sync/main.go +++ b/cmd/data_sync/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "io" @@ -152,7 +153,7 @@ func rsync(syncDir, target, excludeFile string, verboseRsync bool) error { return err } - cmd := exec.Command(path, args...) + cmd := exec.CommandContext(context.Background(), path, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() @@ -218,7 +219,7 @@ func main() { if excludeFile != "" { defer func() { log.Infof("removing exclude file %s", excludeFile) - if err := os.Remove(excludeFile); err != nil { + if rmErr := os.Remove(excludeFile); rmErr != nil { log.Warningf("failed to remove temp file %s", excludeFile) } }() diff --git a/cmd/diskimg/main.go b/cmd/diskimg/main.go index bda07aa..629f5c8 100644 --- a/cmd/diskimg/main.go +++ b/cmd/diskimg/main.go @@ -30,7 +30,7 @@ func openImage(imageFile string) (*os.File, []byte, error) { return nil, nil, err } - if _, err := f.Seek(0, 0); err != nil { + if _, err = f.Seek(0, 0); err != nil { return nil, nil, err } @@ -103,12 +103,12 @@ func main() { die.If(err) if !bytes.Equal(deviceHash, hash) { - fmt.Fprintln(os.Stderr, "Hash mismatch:") - fmt.Fprintf(os.Stderr, "\t%s: %s\n", imageFile, hash) - fmt.Fprintf(os.Stderr, "\t%s: %s\n", devicePath, deviceHash) - os.Exit(1) + buf := &bytes.Buffer{} + fmt.Fprintln(buf, "Hash mismatch:") + fmt.Fprintf(buf, "\t%s: %s\n", imageFile, hash) + fmt.Fprintf(buf, "\t%s: %s\n", devicePath, deviceHash) + die.With(buf.String()) } debug.Println("OK") - os.Exit(0) } diff --git a/cmd/dumpbytes/main.go b/cmd/dumpbytes/main.go index 114d538..039db6b 100644 --- a/cmd/dumpbytes/main.go +++ b/cmd/dumpbytes/main.go @@ -1,6 +1,7 @@ package main import ( + "errors" "flag" "fmt" "io" @@ -37,10 +38,11 @@ func dumpFile(path string, indentLevel int) error { defer file.Close() fmt.Printf("%svar buffer = []byte{\n", indent.String()) + var n int for { buf := make([]byte, 8) - n, err := file.Read(buf) - if err == io.EOF { + n, err = file.Read(buf) + if errors.Is(err, io.EOF) { if n > 0 { fmt.Printf("%s", indent.String()) printBytes(buf[:n]) diff --git a/cmd/eig/main.go b/cmd/eig/main.go index 18354ab..0df23b2 100644 --- a/cmd/eig/main.go +++ b/cmd/eig/main.go @@ -26,7 +26,7 @@ func main() { path = flag.Arg(0) } - fillByte := uint8(*fill) + fillByte := uint8(*fill & 0xff) // #nosec G115 clearing out of bounds bits buf := make([]byte, pageSize) for i := range pageSize { diff --git a/cmd/fragment/main.go b/cmd/fragment/main.go index db1495e..e35a508 100644 --- a/cmd/fragment/main.go +++ b/cmd/fragment/main.go @@ -72,9 +72,7 @@ func main() { if end < start { fmt.Fprintln(os.Stderr, "[!] end < start, swapping values") - tmp := end - end = start - start = tmp + start, end = end, start } var fmtStr string diff --git a/cmd/host/main.go b/cmd/host/main.go index 112cf81..ada9ec1 100644 --- a/cmd/host/main.go +++ b/cmd/host/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "log" @@ -8,7 +9,8 @@ import ( ) func lookupHost(host string) error { - cname, err := net.LookupCNAME(host) + r := &net.Resolver{} + cname, err := r.LookupCNAME(context.Background(), host) if err != nil { return err } @@ -18,7 +20,7 @@ func lookupHost(host string) error { host = cname } - addrs, err := net.LookupHost(host) + addrs, err := r.LookupHost(context.Background(), host) if err != nil { return err } diff --git a/cmd/kgz/README b/cmd/kgz/README index 382ef36..40d1c40 100644 --- a/cmd/kgz/README +++ b/cmd/kgz/README @@ -11,7 +11,10 @@ based on whether the source filename ends in ".gz". Flags: -l level Compression level (0-9). Only meaninful when - compressing a file. + compressing a file. + -u Do not restrict the size during decompression. As + a safeguard against gzip bombs, the maximum size + allowed is 32 * the compressed file size. diff --git a/cmd/kgz/main.go b/cmd/kgz/main.go index 2215769..90f348b 100644 --- a/cmd/kgz/main.go +++ b/cmd/kgz/main.go @@ -40,26 +40,42 @@ func compress(path, target string, level int) error { return nil } -func uncompress(path, target string) error { +func uncompress(path, target string, unrestrict bool) error { sourceFile, err := os.Open(path) if err != nil { return fmt.Errorf("opening file for read: %w", err) } defer sourceFile.Close() + fi, err := sourceFile.Stat() + if err != nil { + return fmt.Errorf("reading file stats: %w", err) + } + + maxDecompressionSize := fi.Size() * 32 + gzipUncompressor, err := gzip.NewReader(sourceFile) if err != nil { return fmt.Errorf("reading gzip headers: %w", err) } defer gzipUncompressor.Close() + var reader io.Reader = &io.LimitedReader{ + R: gzipUncompressor, + N: maxDecompressionSize, + } + + if unrestrict { + reader = gzipUncompressor + } + destFile, err := os.Create(target) if err != nil { return fmt.Errorf("opening file for write: %w", err) } defer destFile.Close() - _, err = io.Copy(destFile, gzipUncompressor) + _, err = io.Copy(destFile, reader) if err != nil { return fmt.Errorf("uncompressing file: %w", err) } @@ -87,8 +103,8 @@ func isDir(path string) bool { file, err := os.Open(path) if err == nil { defer file.Close() - stat, err := file.Stat() - if err != nil { + stat, err2 := file.Stat() + if err2 != nil { return false } @@ -132,8 +148,11 @@ func main() { var level int var path string var target = "." + var err error + var unrestrict bool flag.IntVar(&level, "l", flate.DefaultCompression, "compression level") + flag.BoolVar(&unrestrict, "u", false, "do not restrict decompression") flag.Parse() if flag.NArg() < 1 || flag.NArg() > 2 { @@ -147,30 +166,31 @@ func main() { } if strings.HasSuffix(path, gzipExt) { - target, err := pathForUncompressing(path, target) + target, err = pathForUncompressing(path, target) if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) os.Exit(1) } - err = uncompress(path, target) + err = uncompress(path, target, unrestrict) if err != nil { os.Remove(target) fmt.Fprintf(os.Stderr, "%s\n", err) os.Exit(1) } - } else { - target, err := pathForCompressing(path, target) - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - os.Exit(1) - } + return + } - err = compress(path, target, level) - if err != nil { - os.Remove(target) - fmt.Fprintf(os.Stderr, "%s\n", err) - os.Exit(1) - } + target, err = pathForCompressing(path, target) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) + } + + err = compress(path, target, level) + if err != nil { + os.Remove(target) + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) } } diff --git a/cmd/pembody/main.go b/cmd/pembody/main.go index b7b6b01..f5da330 100644 --- a/cmd/pembody/main.go +++ b/cmd/pembody/main.go @@ -32,7 +32,7 @@ func main() { if p == nil { lib.Errx(lib.ExitFailure, "%s isn't a PEM-encoded file", flag.Arg(0)) } - if _, err := os.Stdout.Write(p.Bytes); err != nil { + if _, err = os.Stdout.Write(p.Bytes); err != nil { lib.Err(lib.ExitFailure, err, "writing body") } } diff --git a/cmd/readchain/main.go b/cmd/readchain/main.go index 8809611..d933f21 100644 --- a/cmd/readchain/main.go +++ b/cmd/readchain/main.go @@ -27,7 +27,8 @@ func main() { break } - cert, err := x509.ParseCertificate(p.Bytes) + var cert *x509.Certificate + cert, err = x509.ParseCertificate(p.Bytes) if err != nil { fmt.Fprintf(os.Stderr, "[!] %s: %v\n", fileName, err) break diff --git a/cmd/rhash/main.go b/cmd/rhash/main.go index 3df2e46..ddb479d 100644 --- a/cmd/rhash/main.go +++ b/cmd/rhash/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "io" @@ -76,7 +77,13 @@ func main() { continue } - resp, err := http.Get(remote) + req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, remote, nil) + if reqErr != nil { + _, _ = lib.Warn(reqErr, "building request for %s", remote) + continue + } + client := &http.Client{} + resp, err := client.Do(req) if err != nil { _, _ = lib.Warn(err, "fetching %s", remote) continue diff --git a/cmd/rolldie/main.go b/cmd/rolldie/main.go index 7fa9696..904eb1b 100644 --- a/cmd/rolldie/main.go +++ b/cmd/rolldie/main.go @@ -18,7 +18,7 @@ func rollDie(count, sides int) []int { var rolls []int for range count { - roll := rand.IntN(sides) + 1 + roll := rand.IntN(sides) + 1 // #nosec G404 sum += roll rolls = append(rolls, roll) } diff --git a/cmd/showimp/main.go b/cmd/showimp/main.go index e565ec3..f83870d 100644 --- a/cmd/showimp/main.go +++ b/cmd/showimp/main.go @@ -75,13 +75,14 @@ func walkFile(path string, _ os.FileInfo, err error) error { for _, importSpec := range f.Imports { importPath := strings.Trim(importSpec.Path.Value, `"`) - if stdLibRegexp.MatchString(importPath) { + switch { + case stdLibRegexp.MatchString(importPath): debug.Println("standard lib:", importPath) continue - } else if strings.HasPrefix(importPath, project) { + case strings.HasPrefix(importPath, project): debug.Println("internal import:", importPath) continue - } else if strings.HasPrefix(importPath, "golang.org/") { + case strings.HasPrefix(importPath, "golang.org/"): debug.Println("extended lib:", importPath) continue } diff --git a/cmd/ski/main.go b/cmd/ski/main.go index 16544dd..3656f5f 100644 --- a/cmd/ski/main.go +++ b/cmd/ski/main.go @@ -5,7 +5,7 @@ import ( "crypto" "crypto/ecdsa" "crypto/rsa" - "crypto/sha1" + "crypto/sha1" // #nosec G505 "crypto/x509" "crypto/x509/pkix" "encoding/asn1" @@ -20,6 +20,11 @@ import ( "git.wntrmute.dev/kyle/goutils/lib" ) +const ( + keyTypeRSA = "RSA" + keyTypeECDSA = "ECDSA" +) + func usage(w io.Writer) { fmt.Fprintf(w, `ski: print subject key info for PEM-encoded files @@ -94,10 +99,10 @@ func parseKey(data []byte) ([]byte, string) { switch p := privInterface.(type) { case *rsa.PrivateKey: priv = p - kt = "RSA" + kt = keyTypeRSA case *ecdsa.PrivateKey: priv = p - kt = "ECDSA" + kt = keyTypeECDSA default: die.With("unknown private key type %T", privInterface) } @@ -116,9 +121,9 @@ func parseCertificate(data []byte) ([]byte, string) { var kt string switch pub.(type) { case *rsa.PublicKey: - kt = "RSA" + kt = keyTypeRSA case *ecdsa.PublicKey: - kt = "ECDSA" + kt = keyTypeECDSA default: die.With("unknown public key type %T", pub) } @@ -136,9 +141,9 @@ func parseCSR(data []byte) ([]byte, string) { var kt string switch pub.(type) { case *rsa.PublicKey: - kt = "RSA" + kt = keyTypeRSA case *ecdsa.PublicKey: - kt = "ECDSA" + kt = keyTypeECDSA default: die.With("unknown public key type %T", pub) } @@ -186,7 +191,7 @@ func main() { continue } - pubHash := sha1.Sum(subPKI.SubjectPublicKey.Bytes) + pubHash := sha1.Sum(subPKI.SubjectPublicKey.Bytes) // #nosec G401 this is the standard pubHashString := dumpHex(pubHash[:]) if ski == "" { ski = pubHashString diff --git a/cmd/sprox/main.go b/cmd/sprox/main.go index 080f818..031c7fe 100644 --- a/cmd/sprox/main.go +++ b/cmd/sprox/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "io" "net" @@ -10,7 +11,7 @@ import ( ) func proxy(conn net.Conn, inside string) error { - proxyConn, err := net.Dial("tcp", inside) + proxyConn, err := (&net.Dialer{}).DialContext(context.Background(), "tcp", inside) if err != nil { return err } @@ -31,18 +32,20 @@ func main() { flag.StringVar(&inside, "p", "4000", "inside port") flag.Parse() - l, err := net.Listen("tcp", "0.0.0.0:"+outside) + lc := &net.ListenConfig{} + l, err := lc.Listen(context.Background(), "tcp", "0.0.0.0:"+outside) die.If(err) for { - conn, err := l.Accept() + var conn net.Conn + conn, err = l.Accept() if err != nil { _, _ = lib.Warn(err, "accept failed") continue } go func() { - if err := proxy(conn, "127.0.0.1:"+inside); err != nil { + if err = proxy(conn, "127.0.0.1:"+inside); err != nil { _, _ = lib.Warn(err, "proxy error") } }() diff --git a/cmd/stealchain-server/main.go b/cmd/stealchain-server/main.go index 71cfb09..bac349c 100644 --- a/cmd/stealchain-server/main.go +++ b/cmd/stealchain-server/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "crypto/rand" "crypto/tls" "crypto/x509" @@ -15,7 +16,7 @@ import ( ) func main() { - cfg := &tls.Config{} + cfg := &tls.Config{} // #nosec G402 var sysRoot, listenAddr, certFile, keyFile string var verify bool @@ -46,7 +47,8 @@ func main() { } cfg.Certificates = append(cfg.Certificates, cert) if sysRoot != "" { - pemList, err := os.ReadFile(sysRoot) + var pemList []byte + pemList, err = os.ReadFile(sysRoot) die.If(err) roots := x509.NewCertPool() @@ -58,14 +60,16 @@ func main() { cfg.RootCAs = roots } - l, err := net.Listen("tcp", listenAddr) + lc := &net.ListenConfig{} + l, err := lc.Listen(context.Background(), "tcp", listenAddr) if err != nil { fmt.Println(err.Error()) os.Exit(1) } for { - conn, err := l.Accept() + var conn net.Conn + conn, err = l.Accept() if err != nil { fmt.Println(err.Error()) continue @@ -79,7 +83,7 @@ func handleConn(conn net.Conn, cfg *tls.Config) { defer conn.Close() raddr := conn.RemoteAddr() tconn := tls.Server(conn, cfg) - if err := tconn.Handshake(); err != nil { + if err := tconn.HandshakeContext(context.Background()); err != nil { fmt.Printf("[+] %v: failed to complete handshake: %v\n", raddr, err) return } diff --git a/cmd/stealchain/main.go b/cmd/stealchain/main.go index 2911cab..b1f6d0a 100644 --- a/cmd/stealchain/main.go +++ b/cmd/stealchain/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "crypto/tls" "crypto/x509" "encoding/pem" @@ -13,7 +14,7 @@ import ( ) func main() { - var cfg = &tls.Config{} + var cfg = &tls.Config{} // #nosec G402 var sysRoot, serverName string flag.StringVar(&sysRoot, "ca", "", "provide an alternate CA bundle") @@ -43,10 +44,13 @@ func main() { if err != nil { site += ":443" } - conn, err := tls.Dial("tcp", site, cfg) - if err != nil { - fmt.Println(err.Error()) - os.Exit(1) + d := &tls.Dialer{Config: cfg} + nc, err := d.DialContext(context.Background(), "tcp", site) + die.If(err) + + conn, ok := nc.(*tls.Conn) + if !ok { + die.With("invalid TLS connection (not a *tls.Conn)") } cs := conn.ConnectionState() @@ -62,6 +66,7 @@ func main() { err = os.WriteFile(site+".pem", chain, 0644) die.If(err) + fmt.Printf("[+] wrote %s.pem.\n", site) } } diff --git a/cmd/tlsinfo/main.go b/cmd/tlsinfo/main.go index 2c09125..6b0efe5 100644 --- a/cmd/tlsinfo/main.go +++ b/cmd/tlsinfo/main.go @@ -1,10 +1,14 @@ package main import ( + "context" "crypto/tls" "crypto/x509" "fmt" "os" + + "git.wntrmute.dev/kyle/goutils/certlib/hosts" + "git.wntrmute.dev/kyle/goutils/die" ) func main() { @@ -13,16 +17,23 @@ func main() { os.Exit(1) } - hostPort := os.Args[1] - conn, err := tls.Dial("tcp", hostPort, &tls.Config{ - InsecureSkipVerify: true, - }) + hostPort, err := hosts.ParseHost(os.Args[1]) + die.If(err) - if err != nil { - fmt.Printf("Failed to connect to the TLS server: %v\n", err) - os.Exit(1) + d := &tls.Dialer{Config: &tls.Config{ + InsecureSkipVerify: true, + }} // #nosec G402 + + nc, err := d.DialContext(context.Background(), "tcp", hostPort.String()) + die.If(err) + + conn, ok := nc.(*tls.Conn) + if !ok { + die.With("invalid TLS connection (not a *tls.Conn)") } + defer conn.Close() + state := conn.ConnectionState() printConnectionDetails(state) } diff --git a/cmd/utc/main.go b/cmd/utc/main.go index aa617ff..9fd14ba 100644 --- a/cmd/utc/main.go +++ b/cmd/utc/main.go @@ -253,15 +253,16 @@ func main() { showTime(time.Now()) os.Exit(0) case 1: - if flag.Arg(0) == "-" { + switch { + case flag.Arg(0) == "-": s := bufio.NewScanner(os.Stdin) for s.Scan() { times = append(times, s.Text()) } - } else if flag.Arg(0) == "help" { + case flag.Arg(0) == "help": usageExamples() - } else { + default: times = flag.Args() } default: diff --git a/cmd/yamll/main.go b/cmd/yamll/main.go index 2573d2f..77da22e 100644 --- a/cmd/yamll/main.go +++ b/cmd/yamll/main.go @@ -11,9 +11,8 @@ import ( type empty struct{} -func errorf(format string, args ...any) { - format += "\n" - fmt.Fprintf(os.Stderr, format, args...) +func errorf(path string, err error) { + fmt.Fprintf(os.Stderr, "%s FAILED: %s\n", path, err) } func usage(w io.Writer) { @@ -45,14 +44,14 @@ func main() { path := "stdin" in, err := io.ReadAll(os.Stdin) if err != nil { - errorf("%s FAILED: %s", path, err) + errorf(path, err) os.Exit(1) } var e empty err = yaml.Unmarshal(in, &e) if err != nil { - errorf("%s FAILED: %s", path, err) + errorf(path, err) os.Exit(1) } @@ -66,14 +65,14 @@ func main() { for _, path := range flag.Args() { in, err := os.ReadFile(path) if err != nil { - errorf("%s FAILED: %s", path, err) + errorf(path, err) continue } var e empty err = yaml.Unmarshal(in, &e) if err != nil { - errorf("%s FAILED: %s", path, err) + errorf(path, err) continue } diff --git a/cmd/zsearch/main.go b/cmd/zsearch/main.go index 5a2e1ac..e6ee35c 100644 --- a/cmd/zsearch/main.go +++ b/cmd/zsearch/main.go @@ -14,16 +14,16 @@ import ( "os" "path/filepath" "regexp" + + "git.wntrmute.dev/kyle/goutils/lib" ) const defaultDirectory = ".git/objects" -func errorf(format string, a ...any) { - fmt.Fprintf(os.Stderr, format, a...) - if format[len(format)-1] != '\n' { - fmt.Fprintf(os.Stderr, "\n") - } -} +// maxDecompressedSize limits how many bytes we will decompress from a zlib +// stream to mitigate decompression bombs (gosec G110). +// Increase this if you expect larger objects. +const maxDecompressedSize int64 = 64 << 30 // 64 GiB func isDir(path string) bool { fi, err := os.Stat(path) @@ -48,17 +48,21 @@ func loadFile(path string) ([]byte, error) { } defer zread.Close() - _, err = io.Copy(buf, zread) - if err != nil { + // Protect against decompression bombs by limiting how much we read. + lr := io.LimitReader(zread, maxDecompressedSize+1) + if _, err = buf.ReadFrom(lr); err != nil { return nil, err } + if int64(buf.Len()) > maxDecompressedSize { + return nil, fmt.Errorf("decompressed size exceeds limit (%d bytes)", maxDecompressedSize) + } return buf.Bytes(), nil } func showFile(path string) { fileData, err := loadFile(path) if err != nil { - errorf("%v", err) + lib.Warn(err, "failed to load %s", path) return } @@ -68,39 +72,71 @@ func showFile(path string) { func searchFile(path string, search *regexp.Regexp) error { file, err := os.Open(path) if err != nil { - errorf("%v", err) + lib.Warn(err, "failed to open %s", path) return err } defer file.Close() zread, err := zlib.NewReader(file) if err != nil { - errorf("%v", err) + lib.Warn(err, "failed to decompress %s", path) return err } defer zread.Close() - zbuf := bufio.NewReader(zread) - if search.MatchReader(zbuf) { - fileData, err := loadFile(path) - if err != nil { - errorf("%v", err) - return err - } - fmt.Printf("%s:\n%s\n", path, fileData) + // Limit how much we scan to avoid DoS via huge decompression. + lr := io.LimitReader(zread, maxDecompressedSize+1) + zbuf := bufio.NewReader(lr) + if !search.MatchReader(zbuf) { + return nil } + + fileData, err := loadFile(path) + if err != nil { + lib.Warn(err, "failed to load %s", path) + return err + } + fmt.Printf("%s:\n%s\n", path, fileData) return nil } func buildWalker(searchExpr *regexp.Regexp) filepath.WalkFunc { return func(path string, info os.FileInfo, _ error) error { - if info.Mode().IsRegular() { - return searchFile(path, searchExpr) + if !info.Mode().IsRegular() { + return nil } - return nil + return searchFile(path, searchExpr) } } +// runSearch compiles the search expression and processes the provided paths. +// It returns an error for fatal conditions; per-file errors are logged. +func runSearch(expr string) error { + search, err := regexp.Compile(expr) + if err != nil { + return fmt.Errorf("invalid regexp: %w", err) + } + + pathList := flag.Args() + if len(pathList) == 0 { + pathList = []string{defaultDirectory} + } + + for _, path := range pathList { + if isDir(path) { + if err2 := filepath.Walk(path, buildWalker(search)); err2 != nil { + return err2 + } + continue + } + if err2 := searchFile(path, search); err2 != nil { + // Non-fatal: keep going, but report it. + lib.Warn(err2, "non-fatal error while searching files") + } + } + return nil +} + func main() { flSearch := flag.String("s", "", "search string (should be an RE2 regular expression)") flag.Parse() @@ -109,29 +145,10 @@ func main() { for _, path := range flag.Args() { showFile(path) } - } else { - search, err := regexp.Compile(*flSearch) - if err != nil { - errorf("Bad regexp: %v", err) - return - } + return + } - pathList := flag.Args() - if len(pathList) == 0 { - pathList = []string{defaultDirectory} - } - - for _, path := range pathList { - if isDir(path) { - if err := filepath.Walk(path, buildWalker(search)); err != nil { - errorf("%v", err) - return - } - } else { - if err := searchFile(path, search); err != nil { - errorf("%v", err) - } - } - } + if err := runSearch(*flSearch); err != nil { + lib.Err(lib.ExitFailure, err, "failed to run search") } }