cmd: finish linting fixes

This commit is contained in:
2025-11-16 11:03:12 -08:00
parent 3ad562b6fa
commit 1eafb638a8
5 changed files with 213 additions and 141 deletions

View File

@@ -64,4 +64,4 @@ workflows:
testbuild:
jobs:
- testbuild
# - lint
- lint

View File

@@ -91,7 +91,7 @@ func TestReset(t *testing.T) {
}
}
const decay = 5 * time.Millisecond
const decay = 25 * time.Millisecond
const maxDuration = 10 * time.Millisecond
const interval = time.Millisecond

View File

@@ -9,7 +9,6 @@ import (
"git.wntrmute.dev/kyle/goutils/certlib"
"git.wntrmute.dev/kyle/goutils/certlib/revoke"
"git.wntrmute.dev/kyle/goutils/die"
"git.wntrmute.dev/kyle/goutils/lib"
)
@@ -29,83 +28,116 @@ func printRevocation(cert *x509.Certificate) {
}
}
func main() {
var caFile, intFile string
var forceIntermediateBundle, revexp, verbose bool
flag.StringVar(&caFile, "ca", "", "CA certificate `bundle`")
flag.StringVar(&intFile, "i", "", "intermediate `bundle`")
flag.BoolVar(&forceIntermediateBundle, "f", false,
"force the use of the intermediate bundle, ignoring any intermediates bundled with certificate")
flag.BoolVar(&revexp, "r", false, "print revocation and expiry information")
flag.BoolVar(&verbose, "v", false, "verbose")
flag.Parse()
type appConfig struct {
caFile, intFile string
forceIntermediateBundle bool
revexp, verbose bool
}
func parseFlags() appConfig {
var cfg appConfig
flag.StringVar(&cfg.caFile, "ca", "", "CA certificate `bundle`")
flag.StringVar(&cfg.intFile, "i", "", "intermediate `bundle`")
flag.BoolVar(&cfg.forceIntermediateBundle, "f", false,
"force the use of the intermediate bundle, ignoring any intermediates bundled with certificate")
flag.BoolVar(&cfg.revexp, "r", false, "print revocation and expiry information")
flag.BoolVar(&cfg.verbose, "v", false, "verbose")
flag.Parse()
return cfg
}
func loadRoots(caFile string, verbose bool) (*x509.CertPool, error) {
if caFile == "" {
return x509.SystemCertPool()
}
var roots *x509.CertPool
if caFile != "" {
var err error
if verbose {
fmt.Println("[+] loading root certificates from", caFile)
}
roots, err = certlib.LoadPEMCertPool(caFile)
die.If(err)
return certlib.LoadPEMCertPool(caFile)
}
var ints *x509.CertPool
if intFile != "" {
var err error
func loadIntermediates(intFile string, verbose bool) (*x509.CertPool, error) {
if intFile == "" {
return x509.NewCertPool(), nil
}
if verbose {
fmt.Println("[+] loading intermediate certificates from", intFile)
}
ints, err = certlib.LoadPEMCertPool(caFile)
die.If(err)
} else {
ints = x509.NewCertPool()
// Note: use intFile here (previously used caFile mistakenly)
return certlib.LoadPEMCertPool(intFile)
}
if flag.NArg() != 1 {
fmt.Fprintf(os.Stderr, "Usage: %s [-ca bundle] [-i bundle] cert",
lib.ProgName())
}
fileData, err := os.ReadFile(flag.Arg(0))
die.If(err)
chain, err := certlib.ParseCertificatesPEM(fileData)
die.If(err)
if verbose {
fmt.Printf("[+] %s has %d certificates\n", flag.Arg(0), len(chain))
}
cert := chain[0]
if len(chain) > 1 {
if !forceIntermediateBundle {
func addBundledIntermediates(chain []*x509.Certificate, pool *x509.CertPool, verbose bool) {
for _, intermediate := range chain[1:] {
if verbose {
fmt.Printf("[+] adding intermediate with SKI %x\n", intermediate.SubjectKeyId)
}
ints.AddCert(intermediate)
}
pool.AddCert(intermediate)
}
}
func verifyCert(cert *x509.Certificate, roots, ints *x509.CertPool) error {
opts := x509.VerifyOptions{
Intermediates: ints,
Roots: roots,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
}
_, err = cert.Verify(opts)
if err != nil {
fmt.Fprintf(os.Stderr, "Verification failed: %v\n", err)
os.Exit(1)
_, err := cert.Verify(opts)
return err
}
if verbose {
func run(cfg appConfig) error {
roots, err := loadRoots(cfg.caFile, cfg.verbose)
if err != nil {
return err
}
ints, err := loadIntermediates(cfg.intFile, cfg.verbose)
if err != nil {
return err
}
if flag.NArg() != 1 {
fmt.Fprintf(os.Stderr, "Usage: %s [-ca bundle] [-i bundle] cert", lib.ProgName())
}
fileData, err := os.ReadFile(flag.Arg(0))
if err != nil {
return err
}
chain, err := certlib.ParseCertificatesPEM(fileData)
if err != nil {
return err
}
if cfg.verbose {
fmt.Printf("[+] %s has %d certificates\n", flag.Arg(0), len(chain))
}
cert := chain[0]
if len(chain) > 1 && !cfg.forceIntermediateBundle {
addBundledIntermediates(chain, ints, cfg.verbose)
}
if err = verifyCert(cert, roots, ints); err != nil {
return fmt.Errorf("certificate verification failed: %w", err)
}
if cfg.verbose {
fmt.Println("OK")
}
if revexp {
if cfg.revexp {
printRevocation(cert)
}
return nil
}
func main() {
cfg := parseFlags()
if err := run(cfg); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}

View File

@@ -10,6 +10,7 @@ import (
"io"
"os"
"path/filepath"
"strings"
"git.wntrmute.dev/kyle/goutils/die"
"git.wntrmute.dev/kyle/goutils/fileutil"
@@ -48,56 +49,71 @@ func linkTarget(target, top string) string {
return target
}
return filepath.Clean(filepath.Join(target, top))
return filepath.Clean(filepath.Join(top, target))
}
func processFile(tfr *tar.Reader, hdr *tar.Header, top string) error {
if verbose {
fmt.Println(hdr.Name)
// safeJoin joins base and elem and ensures the resulting path does not escape base.
func safeJoin(base, elem string) (string, error) {
cleanBase := filepath.Clean(base)
joined := filepath.Clean(filepath.Join(cleanBase, elem))
absBase, err := filepath.Abs(cleanBase)
if err != nil {
return "", err
}
absJoined, err := filepath.Abs(joined)
if err != nil {
return "", err
}
rel, err := filepath.Rel(absBase, absJoined)
if err != nil {
return "", err
}
if rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
return "", fmt.Errorf("path traversal detected: %s escapes %s", elem, base)
}
return joined, nil
}
filePath := filepath.Clean(filepath.Join(top, hdr.Name))
switch hdr.Typeflag {
case tar.TypeReg:
func handleTypeReg(tfr *tar.Reader, hdr *tar.Header, filePath string) error {
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, tfr)
if err != nil {
if _, err = io.Copy(file, tfr); err != nil {
return err
}
err = setupFile(hdr, file)
if err != nil {
return err
return setupFile(hdr, file)
}
case tar.TypeLink:
func handleTypeLink(hdr *tar.Header, top, filePath string) error {
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
source, err := os.Open(hdr.Linkname)
srcPath, err := safeJoin(top, hdr.Linkname)
if err != nil {
return err
}
_, err = io.Copy(file, source)
source, err := os.Open(srcPath)
if err != nil {
return err
}
defer source.Close()
err = setupFile(hdr, file)
if err != nil {
if _, err = io.Copy(file, source); err != nil {
return err
}
case tar.TypeSymlink:
return setupFile(hdr, file)
}
func handleTypeSymlink(hdr *tar.Header, top, filePath string) error {
if !fileutil.ValidateSymlink(hdr.Linkname, top) {
return fmt.Errorf("symlink %s is outside the top-level %s",
hdr.Linkname, top)
return fmt.Errorf("symlink %s is outside the top-level %s", hdr.Linkname, top)
}
path := linkTarget(hdr.Linkname, top)
if ok, err := filepath.Match(top+"/*", filepath.Clean(path)); !ok {
@@ -105,18 +121,33 @@ func processFile(tfr *tar.Reader, hdr *tar.Header, top string) error {
} else if err != nil {
return err
}
return os.Symlink(linkTarget(hdr.Linkname, top), filePath)
}
err := os.Symlink(linkTarget(hdr.Linkname, top), filePath)
func handleTypeDir(hdr *tar.Header, filePath string) error {
return os.MkdirAll(filePath, os.FileMode(hdr.Mode&0xFFFFFFFF)) // #nosec G115
}
func processFile(tfr *tar.Reader, hdr *tar.Header, top string) error {
if verbose {
fmt.Println(hdr.Name)
}
filePath, err := safeJoin(top, hdr.Name)
if err != nil {
return err
}
switch hdr.Typeflag {
case tar.TypeReg:
return handleTypeReg(tfr, hdr, filePath)
case tar.TypeLink:
return handleTypeLink(hdr, top, filePath)
case tar.TypeSymlink:
return handleTypeSymlink(hdr, top, filePath)
case tar.TypeDir:
err := os.MkdirAll(filePath, os.FileMode(hdr.Mode&0xFFFFFFFF)) // #nosec G115
if err != nil {
return err
return handleTypeDir(hdr, filePath)
}
}
return nil
}

View File

@@ -96,6 +96,44 @@ func init() {
flag.Usage = func() { usage(os.Stdout) }
}
type options struct {
dryRun, force, printChanged, verbose bool
}
func processOne(file string, opt options) error {
renamed, err := newName(file)
if err != nil {
_, _ = lib.Warn(err, "failed to get new file name")
return err
}
if opt.verbose && !opt.printChanged {
fmt.Fprintln(os.Stdout, file)
}
if renamed == file {
return nil
}
if !opt.dryRun {
if err = move(renamed, file, opt.force); err != nil {
_, _ = lib.Warn(err, "failed to rename file from %s to %s", file, renamed)
return err
}
}
if opt.printChanged && !opt.verbose {
fmt.Fprintln(os.Stdout, file, "->", renamed)
}
return nil
}
func run(dryRun, force, printChanged, verbose bool, files []string) {
if verbose && printChanged {
printChanged = false
}
opt := options{dryRun: dryRun, force: force, printChanged: printChanged, verbose: verbose}
for _, file := range files {
_ = processOne(file, opt)
}
}
func main() {
var dryRun, force, printChanged, verbose bool
flag.BoolVar(&force, "f", false, "force overwriting of files if there is a collision")
@@ -104,34 +142,5 @@ func main() {
flag.BoolVar(&verbose, "v", false, "list all processed files")
flag.Parse()
if verbose && printChanged {
printChanged = false
}
for _, file := range flag.Args() {
renamed, err := newName(file)
if err != nil {
_, _ = lib.Warn(err, "failed to get new file name")
continue
}
if verbose && !printChanged {
fmt.Fprintln(os.Stdout, file)
}
if renamed != file {
if !dryRun {
err = move(renamed, file, force)
if err != nil {
_, _ = lib.Warn(err, "failed to rename file from %s to %s", file, renamed)
continue
}
}
if printChanged && !verbose {
fmt.Fprintln(os.Stdout, file, "->", renamed)
}
}
}
run(dryRun, force, printChanged, verbose, flag.Args())
}