cmd: start linting fixes.

This commit is contained in:
2025-11-16 00:36:19 -08:00
parent a573f1cd20
commit f31d74243f
25 changed files with 662 additions and 599 deletions

View File

@@ -18,6 +18,19 @@ issues:
# Default: 3 # Default: 3
max-same-issues: 50 max-same-issues: 50
# Exclude some lints for CLI programs under cmd/ (package main).
# The project allows fmt.Print* in command-line tools; keep forbidigo for libraries.
exclude-rules:
- path: ^cmd/
linters:
- forbidigo
- path: cmd/.*
linters:
- forbidigo
- path: .*/cmd/.*
linters:
- forbidigo
formatters: formatters:
enable: enable:
- goimports # checks if the code and import statements are formatted according to the 'goimports' command - goimports # checks if the code and import statements are formatted according to the 'goimports' command

View File

@@ -3,12 +3,12 @@ package main
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"flag"
"errors" "errors"
"flag"
"fmt" "fmt"
"io/ioutil"
"net" "net"
"os" "os"
"strings"
"time" "time"
"git.wntrmute.dev/kyle/goutils/certlib" "git.wntrmute.dev/kyle/goutils/certlib"
@@ -23,6 +23,13 @@ var (
verbose bool verbose bool
) )
var (
strOK = "OK"
strExpired = "EXPIRED"
strRevoked = "REVOKED"
strUnknown = "UNKNOWN"
)
func main() { func main() {
flag.BoolVar(&hardfail, "hardfail", false, "treat revocation check failures as fatal") flag.BoolVar(&hardfail, "hardfail", false, "treat revocation check failures as fatal")
flag.DurationVar(&timeout, "timeout", 10*time.Second, "network timeout for OCSP/CRL fetches and TLS site connects") flag.DurationVar(&timeout, "timeout", 10*time.Second, "network timeout for OCSP/CRL fetches and TLS site connects")
@@ -42,16 +49,16 @@ func main() {
for _, target := range flag.Args() { for _, target := range flag.Args() {
status, err := processTarget(target) status, err := processTarget(target)
switch status { switch status {
case "OK": case strOK:
fmt.Printf("%s: OK\n", target) fmt.Printf("%s: %s\n", target, strOK)
case "EXPIRED": case strExpired:
fmt.Printf("%s: EXPIRED: %v\n", target, err) fmt.Printf("%s: %s: %v\n", target, strExpired, err)
exitCode = 1 exitCode = 1
case "REVOKED": case strRevoked:
fmt.Printf("%s: REVOKED\n", target) fmt.Printf("%s: %s\n", target, strRevoked)
exitCode = 1 exitCode = 1
case "UNKNOWN": case strUnknown:
fmt.Printf("%s: UNKNOWN: %v\n", target, err) fmt.Printf("%s: %s: %v\n", target, strUnknown, err)
if hardfail { if hardfail {
// In hardfail, treat unknown as failure // In hardfail, treat unknown as failure
exitCode = 1 exitCode = 1
@@ -67,74 +74,67 @@ func processTarget(target string) (string, error) {
return checkFile(target) return checkFile(target)
} }
// Not a file; treat as site
return checkSite(target) return checkSite(target)
} }
func checkFile(path string) (string, error) { func checkFile(path string) (string, error) {
in, err := ioutil.ReadFile(path) // Prefer high-level helpers from certlib to load certificates from disk
if err != nil { if certs, err := certlib.LoadCertificates(path); err == nil && len(certs) > 0 {
return "UNKNOWN", err
}
// Try PEM first; if that fails, try single DER cert
certs, err := certlib.ReadCertificates(in)
if err != nil || len(certs) == 0 {
cert, _, derr := certlib.ReadCertificate(in)
if derr != nil || cert == nil {
if err == nil {
err = derr
}
return "UNKNOWN", err
}
return evaluateCert(cert)
}
// Evaluate the first certificate (leaf) by default // Evaluate the first certificate (leaf) by default
return evaluateCert(certs[0]) return evaluateCert(certs[0])
}
cert, err := certlib.LoadCertificate(path)
if err != nil || cert == nil {
return strUnknown, err
}
return evaluateCert(cert)
} }
func checkSite(hostport string) (string, error) { func checkSite(hostport string) (string, error) {
// Use certlib/hosts to parse host/port (supports https URLs and host:port) // Use certlib/hosts to parse host/port (supports https URLs and host:port)
target, err := hosts.ParseHost(hostport) target, err := hosts.ParseHost(hostport)
if err != nil { if err != nil {
return "UNKNOWN", err return strUnknown, err
} }
d := &net.Dialer{Timeout: timeout} d := &net.Dialer{Timeout: timeout}
conn, err := tls.DialWithDialer(d, "tcp", target.String(), &tls.Config{InsecureSkipVerify: true, ServerName: target.Host}) conn, err := tls.DialWithDialer(
d,
"tcp",
target.String(),
&tls.Config{InsecureSkipVerify: true, ServerName: target.Host}, // #nosec G402
)
if err != nil { if err != nil {
return "UNKNOWN", err return strUnknown, err
} }
defer conn.Close() defer conn.Close()
state := conn.ConnectionState() state := conn.ConnectionState()
if len(state.PeerCertificates) == 0 { if len(state.PeerCertificates) == 0 {
return "UNKNOWN", errors.New("no peer certificates presented") return strUnknown, errors.New("no peer certificates presented")
} }
return evaluateCert(state.PeerCertificates[0]) return evaluateCert(state.PeerCertificates[0])
} }
func evaluateCert(cert *x509.Certificate) (string, error) { func evaluateCert(cert *x509.Certificate) (string, error) {
// Expiry check // Delegate validity and revocation checks to certlib/revoke helper.
now := time.Now() // It returns revoked=true for both revoked and expired/not-yet-valid.
if !now.Before(cert.NotAfter) { // Map those cases back to our statuses using the returned error text.
return "EXPIRED", fmt.Errorf("expired at %s", cert.NotAfter)
}
if !now.After(cert.NotBefore) {
return "EXPIRED", fmt.Errorf("not valid until %s", cert.NotBefore)
}
// Revocation check using certlib/revoke
revoked, ok, err := revoke.VerifyCertificateError(cert) revoked, ok, err := revoke.VerifyCertificateError(cert)
if revoked { if revoked {
// If revoked is true, ok will be true per implementation, err may describe why if err != nil {
return "REVOKED", err msg := err.Error()
if strings.Contains(msg, "expired") || strings.Contains(msg, "isn't valid until") || strings.Contains(msg, "not valid until") {
return strExpired, err
}
}
return strRevoked, err
} }
if !ok { if !ok {
// Revocation status could not be determined // Revocation status could not be determined
return "UNKNOWN", err return strUnknown, err
} }
return "OK", nil return strOK, nil
} }

View File

@@ -5,6 +5,7 @@ import (
"encoding/pem" "encoding/pem"
"flag" "flag"
"fmt" "fmt"
"os"
"regexp" "regexp"
"git.wntrmute.dev/kyle/goutils/die" "git.wntrmute.dev/kyle/goutils/die"
@@ -34,6 +35,6 @@ func main() {
chain += string(pem.EncodeToMemory(&p)) chain += string(pem.EncodeToMemory(&p))
} }
fmt.Println(chain) fmt.Fprintln(os.Stdout, chain)
} }
} }

View File

@@ -101,30 +101,30 @@ func extUsage(ext []x509.ExtKeyUsage) string {
} }
func showBasicConstraints(cert *x509.Certificate) { func showBasicConstraints(cert *x509.Certificate) {
fmt.Printf("\tBasic constraints: ") fmt.Fprint(os.Stdout, "\tBasic constraints: ")
if cert.BasicConstraintsValid { if cert.BasicConstraintsValid {
fmt.Printf("valid") fmt.Fprint(os.Stdout, "valid")
} else { } else {
fmt.Printf("invalid") fmt.Fprint(os.Stdout, "invalid")
} }
if cert.IsCA { if cert.IsCA {
fmt.Printf(", is a CA certificate") fmt.Fprint(os.Stdout, ", is a CA certificate")
if !cert.BasicConstraintsValid { if !cert.BasicConstraintsValid {
fmt.Printf(" (basic constraint failure)") fmt.Fprint(os.Stdout, " (basic constraint failure)")
} }
} else { } else {
fmt.Printf("is not a CA certificate") fmt.Fprint(os.Stdout, "is not a CA certificate")
if cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0 { if cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0 {
fmt.Printf(" (key encipherment usage enabled!)") fmt.Fprint(os.Stdout, " (key encipherment usage enabled!)")
} }
} }
if (cert.MaxPathLen == 0 && cert.MaxPathLenZero) || (cert.MaxPathLen > 0) { if (cert.MaxPathLen == 0 && cert.MaxPathLenZero) || (cert.MaxPathLen > 0) {
fmt.Printf(", max path length %d", cert.MaxPathLen) fmt.Fprintf(os.Stdout, ", max path length %d", cert.MaxPathLen)
} }
fmt.Printf("\n") fmt.Fprintln(os.Stdout)
} }
const oneTrueDateFormat = "2006-01-02T15:04:05-0700" const oneTrueDateFormat = "2006-01-02T15:04:05-0700"
@@ -140,35 +140,35 @@ func wrapPrint(text string, indent int) {
tabs += "\t" tabs += "\t"
} }
fmt.Printf(tabs+"%s\n", wrap(text, indent)) fmt.Fprintf(os.Stdout, tabs+"%s\n", wrap(text, indent))
} }
func displayCert(cert *x509.Certificate) { func displayCert(cert *x509.Certificate) {
fmt.Println("CERTIFICATE") fmt.Fprintln(os.Stdout, "CERTIFICATE")
if showHash { if showHash {
fmt.Println(wrap(fmt.Sprintf("SHA256: %x", sha256.Sum256(cert.Raw)), 0)) fmt.Fprintln(os.Stdout, wrap(fmt.Sprintf("SHA256: %x", sha256.Sum256(cert.Raw)), 0))
} }
fmt.Println(wrap("Subject: "+displayName(cert.Subject), 0)) fmt.Fprintln(os.Stdout, wrap("Subject: "+displayName(cert.Subject), 0))
fmt.Println(wrap("Issuer: "+displayName(cert.Issuer), 0)) fmt.Fprintln(os.Stdout, wrap("Issuer: "+displayName(cert.Issuer), 0))
fmt.Printf("\tSignature algorithm: %s / %s\n", sigAlgoPK(cert.SignatureAlgorithm), fmt.Fprintf(os.Stdout, "\tSignature algorithm: %s / %s\n", sigAlgoPK(cert.SignatureAlgorithm),
sigAlgoHash(cert.SignatureAlgorithm)) sigAlgoHash(cert.SignatureAlgorithm))
fmt.Println("Details:") fmt.Fprintln(os.Stdout, "Details:")
wrapPrint("Public key: "+certPublic(cert), 1) wrapPrint("Public key: "+certPublic(cert), 1)
fmt.Printf("\tSerial number: %s\n", cert.SerialNumber) fmt.Fprintf(os.Stdout, "\tSerial number: %s\n", cert.SerialNumber)
if len(cert.AuthorityKeyId) > 0 { if len(cert.AuthorityKeyId) > 0 {
fmt.Printf("\t%s\n", wrap("AKI: "+dumpHex(cert.AuthorityKeyId), 1)) fmt.Fprintf(os.Stdout, "\t%s\n", wrap("AKI: "+dumpHex(cert.AuthorityKeyId), 1))
} }
if len(cert.SubjectKeyId) > 0 { if len(cert.SubjectKeyId) > 0 {
fmt.Printf("\t%s\n", wrap("SKI: "+dumpHex(cert.SubjectKeyId), 1)) fmt.Fprintf(os.Stdout, "\t%s\n", wrap("SKI: "+dumpHex(cert.SubjectKeyId), 1))
} }
wrapPrint("Valid from: "+cert.NotBefore.Format(dateFormat), 1) wrapPrint("Valid from: "+cert.NotBefore.Format(dateFormat), 1)
fmt.Printf("\t until: %s\n", cert.NotAfter.Format(dateFormat)) fmt.Fprintf(os.Stdout, "\t until: %s\n", cert.NotAfter.Format(dateFormat))
fmt.Printf("\tKey usages: %s\n", keyUsages(cert.KeyUsage)) fmt.Fprintf(os.Stdout, "\tKey usages: %s\n", keyUsages(cert.KeyUsage))
if len(cert.ExtKeyUsage) > 0 { if len(cert.ExtKeyUsage) > 0 {
fmt.Printf("\tExtended usages: %s\n", extUsage(cert.ExtKeyUsage)) fmt.Fprintf(os.Stdout, "\tExtended usages: %s\n", extUsage(cert.ExtKeyUsage))
} }
showBasicConstraints(cert) showBasicConstraints(cert)
@@ -221,13 +221,13 @@ func displayAllCerts(in []byte, leafOnly bool) {
if err != nil { if err != nil {
certs, _, err = certlib.ParseCertificatesDER(in, "") certs, _, err = certlib.ParseCertificatesDER(in, "")
if err != nil { if err != nil {
lib.Warn(err, "failed to parse certificates") _, _ = lib.Warn(err, "failed to parse certificates")
return return
} }
} }
if len(certs) == 0 { if len(certs) == 0 {
lib.Warnx("no certificates found") _, _ = lib.Warnx("no certificates found")
return return
} }
@@ -245,7 +245,7 @@ func displayAllCertsWeb(uri string, leafOnly bool) {
ci := getConnInfo(uri) ci := getConnInfo(uri)
conn, err := tls.Dial("tcp", ci.Addr, permissiveConfig()) conn, err := tls.Dial("tcp", ci.Addr, permissiveConfig())
if err != nil { if err != nil {
lib.Warn(err, "couldn't connect to %s", ci.Addr) _, _ = lib.Warn(err, "couldn't connect to %s", ci.Addr)
return return
} }
defer conn.Close() defer conn.Close()
@@ -261,11 +261,11 @@ func displayAllCertsWeb(uri string, leafOnly bool) {
} }
conn.Close() conn.Close()
} else { } else {
lib.Warn(err, "TLS verification error with server name %s", ci.Host) _, _ = lib.Warn(err, "TLS verification error with server name %s", ci.Host)
} }
if len(state.PeerCertificates) == 0 { if len(state.PeerCertificates) == 0 {
lib.Warnx("no certificates found") _, _ = lib.Warnx("no certificates found")
return return
} }
@@ -275,14 +275,14 @@ func displayAllCertsWeb(uri string, leafOnly bool) {
} }
if len(state.VerifiedChains) == 0 { if len(state.VerifiedChains) == 0 {
lib.Warnx("no verified chains found; using peer chain") _, _ = lib.Warnx("no verified chains found; using peer chain")
for i := range state.PeerCertificates { for i := range state.PeerCertificates {
displayCert(state.PeerCertificates[i]) displayCert(state.PeerCertificates[i])
} }
} else { } else {
fmt.Println("TLS chain verified successfully.") fmt.Fprintln(os.Stdout, "TLS chain verified successfully.")
for i := range state.VerifiedChains { for i := range state.VerifiedChains {
fmt.Printf("--- Verified certificate chain %d ---\n", i+1) fmt.Fprintf(os.Stdout, "--- Verified certificate chain %d ---%s", i+1, "\n")
for j := range state.VerifiedChains[i] { for j := range state.VerifiedChains[i] {
displayCert(state.VerifiedChains[i][j]) displayCert(state.VerifiedChains[i][j])
} }
@@ -300,7 +300,7 @@ func main() {
if flag.NArg() == 0 || (flag.NArg() == 1 && flag.Arg(0) == "-") { if flag.NArg() == 0 || (flag.NArg() == 1 && flag.Arg(0) == "-") {
certs, err := io.ReadAll(os.Stdin) certs, err := io.ReadAll(os.Stdin)
if err != nil { if err != nil {
lib.Warn(err, "couldn't read certificates from standard input") _, _ = lib.Warn(err, "couldn't read certificates from standard input")
os.Exit(1) os.Exit(1)
} }
@@ -311,13 +311,13 @@ func main() {
displayAllCerts(certs, leafOnly) displayAllCerts(certs, leafOnly)
} else { } else {
for _, filename := range flag.Args() { for _, filename := range flag.Args() {
fmt.Printf("--%s ---\n", filename) fmt.Fprintf(os.Stdout, "--%s ---%s", filename, "\n")
if strings.HasPrefix(filename, "https://") { if strings.HasPrefix(filename, "https://") {
displayAllCertsWeb(filename, leafOnly) displayAllCertsWeb(filename, leafOnly)
} else { } else {
in, err := os.ReadFile(filename) in, err := os.ReadFile(filename)
if err != nil { if err != nil {
lib.Warn(err, "couldn't read certificate") _, _ = lib.Warn(err, "couldn't read certificate")
continue continue
} }

View File

@@ -38,16 +38,22 @@ var extKeyUsages = map[x509.ExtKeyUsage]string{
x509.ExtKeyUsageOCSPSigning: "ocsp signing", x509.ExtKeyUsageOCSPSigning: "ocsp signing",
x509.ExtKeyUsageMicrosoftServerGatedCrypto: "microsoft sgc", x509.ExtKeyUsageMicrosoftServerGatedCrypto: "microsoft sgc",
x509.ExtKeyUsageNetscapeServerGatedCrypto: "netscape sgc", x509.ExtKeyUsageNetscapeServerGatedCrypto: "netscape sgc",
x509.ExtKeyUsageMicrosoftCommercialCodeSigning: "microsoft commercial code signing",
x509.ExtKeyUsageMicrosoftKernelCodeSigning: "microsoft kernel code signing",
} }
func pubKeyAlgo(a x509.PublicKeyAlgorithm) string { func pubKeyAlgo(a x509.PublicKeyAlgorithm) string {
switch a { switch a {
case x509.UnknownPublicKeyAlgorithm:
return "unknown public key algorithm"
case x509.RSA: case x509.RSA:
return "RSA" return "RSA"
case x509.ECDSA: case x509.ECDSA:
return "ECDSA" return "ECDSA"
case x509.DSA: case x509.DSA:
return "DSA" return "DSA"
case x509.Ed25519:
return "Ed25519"
default: default:
return "unknown public key algorithm" return "unknown public key algorithm"
} }
@@ -55,13 +61,18 @@ func pubKeyAlgo(a x509.PublicKeyAlgorithm) string {
func sigAlgoPK(a x509.SignatureAlgorithm) string { func sigAlgoPK(a x509.SignatureAlgorithm) string {
switch a { switch a {
case x509.MD2WithRSA, x509.MD5WithRSA, x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA: case x509.MD2WithRSA, x509.MD5WithRSA, x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA:
return "RSA" return "RSA"
case x509.SHA256WithRSAPSS, x509.SHA384WithRSAPSS, x509.SHA512WithRSAPSS:
return "RSA-PSS"
case x509.ECDSAWithSHA1, x509.ECDSAWithSHA256, x509.ECDSAWithSHA384, x509.ECDSAWithSHA512: case x509.ECDSAWithSHA1, x509.ECDSAWithSHA256, x509.ECDSAWithSHA384, x509.ECDSAWithSHA512:
return "ECDSA" return "ECDSA"
case x509.DSAWithSHA1, x509.DSAWithSHA256: case x509.DSAWithSHA1, x509.DSAWithSHA256:
return "DSA" return "DSA"
case x509.PureEd25519:
return "Ed25519"
case x509.UnknownSignatureAlgorithm:
return "unknown public key algorithm"
default: default:
return "unknown public key algorithm" return "unknown public key algorithm"
} }
@@ -77,10 +88,20 @@ func sigAlgoHash(a x509.SignatureAlgorithm) string {
return "SHA1" return "SHA1"
case x509.SHA256WithRSA, x509.ECDSAWithSHA256, x509.DSAWithSHA256: case x509.SHA256WithRSA, x509.ECDSAWithSHA256, x509.DSAWithSHA256:
return "SHA256" return "SHA256"
case x509.SHA256WithRSAPSS:
return "SHA256"
case x509.SHA384WithRSA, x509.ECDSAWithSHA384: case x509.SHA384WithRSA, x509.ECDSAWithSHA384:
return "SHA384" return "SHA384"
case x509.SHA384WithRSAPSS:
return "SHA384"
case x509.SHA512WithRSA, x509.ECDSAWithSHA512: case x509.SHA512WithRSA, x509.ECDSAWithSHA512:
return "SHA512" return "SHA512"
case x509.SHA512WithRSAPSS:
return "SHA512"
case x509.PureEd25519:
return "SHA512"
case x509.UnknownSignatureAlgorithm:
return "unknown hash algorithm"
default: default:
return "unknown hash algorithm" return "unknown hash algorithm"
} }

View File

@@ -5,7 +5,6 @@ import (
"crypto/x509/pkix" "crypto/x509/pkix"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"strings" "strings"
"time" "time"
@@ -81,15 +80,15 @@ func main() {
flag.Parse() flag.Parse()
for _, file := range flag.Args() { for _, file := range flag.Args() {
in, err := ioutil.ReadFile(file) in, err := os.ReadFile(file)
if err != nil { if err != nil {
lib.Warn(err, "failed to read file") _, _ = lib.Warn(err, "failed to read file")
continue continue
} }
certs, err := certlib.ParseCertificatesPEM(in) certs, err := certlib.ParseCertificatesPEM(in)
if err != nil { if err != nil {
lib.Warn(err, "while parsing certificates") _, _ = lib.Warn(err, "while parsing certificates")
continue continue
} }

View File

@@ -5,6 +5,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"errors"
"log" "log"
"net" "net"
"os" "os"
@@ -93,7 +94,7 @@ func exec(wg *sync.WaitGroup, user, host string, commands []string) {
defer func() { defer func() {
for i := len(shutdown) - 1; i >= 0; i-- { for i := len(shutdown) - 1; i >= 0; i-- {
err := shutdown[i]() err := shutdown[i]()
if err != nil && err != io.EOF { if err != nil && !errors.Is(err, io.EOF) {
logError(host, err, "shutting down") logError(host, err, "shutting down")
} }
} }
@@ -150,7 +151,7 @@ func upload(wg *sync.WaitGroup, user, host, local, remote string) {
defer func() { defer func() {
for i := len(shutdown) - 1; i >= 0; i-- { for i := len(shutdown) - 1; i >= 0; i-- {
err := shutdown[i]() err := shutdown[i]()
if err != nil && err != io.EOF { if err != nil && !errors.Is(err, io.EOF) {
logError(host, err, "shutting down") logError(host, err, "shutting down")
} }
} }
@@ -199,7 +200,7 @@ func upload(wg *sync.WaitGroup, user, host, local, remote string) {
fmt.Printf("[%s] wrote %d-byte chunk\n", host, n) fmt.Printf("[%s] wrote %d-byte chunk\n", host, n)
} }
if err == io.EOF { if errors.Is(err, io.EOF) {
break break
} else if err != nil { } else if err != nil {
logError(host, err, "reading chunk") logError(host, err, "reading chunk")
@@ -215,7 +216,7 @@ func download(wg *sync.WaitGroup, user, host, local, remote string) {
defer func() { defer func() {
for i := len(shutdown) - 1; i >= 0; i-- { for i := len(shutdown) - 1; i >= 0; i-- {
err := shutdown[i]() err := shutdown[i]()
if err != nil && err != io.EOF { if err != nil && !errors.Is(err, io.EOF) {
logError(host, err, "shutting down") logError(host, err, "shutting down")
} }
} }
@@ -265,7 +266,7 @@ func download(wg *sync.WaitGroup, user, host, local, remote string) {
fmt.Printf("[%s] wrote %d-byte chunk\n", host, n) fmt.Printf("[%s] wrote %d-byte chunk\n", host, n)
} }
if err == io.EOF { if errors.Is(err, io.EOF) {
break break
} else if err != nil { } else if err != nil {
logError(host, err, "reading chunk") logError(host, err, "reading chunk")

View File

@@ -263,7 +263,7 @@ func main() {
tfr := tar.NewReader(r) tfr := tar.NewReader(r)
for { for {
hdr, err := tfr.Next() hdr, err := tfr.Next()
if err == io.EOF { if errors.Is(err, io.EOF) {
break break
} }
die.If(err) die.If(err)

View File

@@ -7,8 +7,7 @@ import (
"encoding/pem" "encoding/pem"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "os"
"log"
"git.wntrmute.dev/kyle/goutils/die" "git.wntrmute.dev/kyle/goutils/die"
) )
@@ -17,12 +16,12 @@ func main() {
flag.Parse() flag.Parse()
for _, fileName := range flag.Args() { for _, fileName := range flag.Args() {
in, err := ioutil.ReadFile(fileName) in, err := os.ReadFile(fileName)
die.If(err) die.If(err)
if p, _ := pem.Decode(in); p != nil { if p, _ := pem.Decode(in); p != nil {
if p.Type != "CERTIFICATE REQUEST" { if p.Type != "CERTIFICATE REQUEST" {
log.Fatal("INVALID FILE TYPE") die.With("INVALID FILE TYPE")
} }
in = p.Bytes in = p.Bytes
} }
@@ -48,8 +47,8 @@ func main() {
Bytes: out, Bytes: out,
} }
err = ioutil.WriteFile(fileName+".pub", pem.EncodeToMemory(p), 0644) err = os.WriteFile(fileName+".pub", pem.EncodeToMemory(p), 0o644)
die.If(err) die.If(err)
fmt.Printf("[+] wrote %s.\n", fileName+".pub") fmt.Fprintf(os.Stdout, "[+] wrote %s.\n", fileName+".pub")
} }
} }

View File

@@ -98,9 +98,9 @@ func main() {
fmtStr += "\n" fmtStr += "\n"
for i := start; !endFunc(i); i++ { for i := start; !endFunc(i); i++ {
if *quiet { if *quiet {
fmt.Println(lines[i]) fmt.Fprintln(os.Stdout, lines[i])
} else { } else {
fmt.Printf(fmtStr, i, lines[i]) fmt.Fprintf(os.Stdout, fmtStr, i, lines[i])
} }
} }
} }

View File

@@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io"
"os" "os"
"git.wntrmute.dev/kyle/goutils/lib" "git.wntrmute.dev/kyle/goutils/lib"
@@ -16,20 +16,20 @@ func prettify(file string, validateOnly bool) error {
var err error var err error
if file == "-" { if file == "-" {
in, err = ioutil.ReadAll(os.Stdin) in, err = io.ReadAll(os.Stdin)
} else { } else {
in, err = ioutil.ReadFile(file) in, err = os.ReadFile(file)
} }
if err != nil { if err != nil {
lib.Warn(err, "ReadFile") _, _ = lib.Warn(err, "ReadFile")
return err return err
} }
var buf = &bytes.Buffer{} var buf = &bytes.Buffer{}
err = json.Indent(buf, in, "", " ") err = json.Indent(buf, in, "", " ")
if err != nil { if err != nil {
lib.Warn(err, "%s", file) _, _ = lib.Warn(err, "%s", file)
return err return err
} }
@@ -40,11 +40,11 @@ func prettify(file string, validateOnly bool) error {
if file == "-" { if file == "-" {
_, err = os.Stdout.Write(buf.Bytes()) _, err = os.Stdout.Write(buf.Bytes())
} else { } else {
err = ioutil.WriteFile(file, buf.Bytes(), 0644) err = os.WriteFile(file, buf.Bytes(), 0o644)
} }
if err != nil { if err != nil {
lib.Warn(err, "WriteFile") _, _ = lib.Warn(err, "WriteFile")
} }
return err return err
@@ -55,20 +55,20 @@ func compact(file string, validateOnly bool) error {
var err error var err error
if file == "-" { if file == "-" {
in, err = ioutil.ReadAll(os.Stdin) in, err = io.ReadAll(os.Stdin)
} else { } else {
in, err = ioutil.ReadFile(file) in, err = os.ReadFile(file)
} }
if err != nil { if err != nil {
lib.Warn(err, "ReadFile") _, _ = lib.Warn(err, "ReadFile")
return err return err
} }
var buf = &bytes.Buffer{} var buf = &bytes.Buffer{}
err = json.Compact(buf, in) err = json.Compact(buf, in)
if err != nil { if err != nil {
lib.Warn(err, "%s", file) _, _ = lib.Warn(err, "%s", file)
return err return err
} }
@@ -79,11 +79,11 @@ func compact(file string, validateOnly bool) error {
if file == "-" { if file == "-" {
_, err = os.Stdout.Write(buf.Bytes()) _, err = os.Stdout.Write(buf.Bytes())
} else { } else {
err = ioutil.WriteFile(file, buf.Bytes(), 0644) err = os.WriteFile(file, buf.Bytes(), 0o644)
} }
if err != nil { if err != nil {
lib.Warn(err, "WriteFile") _, _ = lib.Warn(err, "WriteFile")
} }
return err return err
@@ -91,7 +91,7 @@ func compact(file string, validateOnly bool) error {
func usage() { func usage() {
progname := lib.ProgName() progname := lib.ProgName()
fmt.Printf(`Usage: %s [-h] files... fmt.Fprintf(os.Stdout, `Usage: %s [-h] files...
%s is used to lint and prettify (or compact) JSON files. The %s is used to lint and prettify (or compact) JSON files. The
files will be updated in-place. files will be updated in-place.

View File

@@ -49,5 +49,5 @@ func main() {
code := kind << 6 code := kind << 6
code += (min << 3) code += (min << 3)
code += max code += max
fmt.Printf("%0o\n", code) fmt.Fprintf(os.Stdout, "%0o\n", code)
} }

View File

@@ -3,8 +3,7 @@ package main
import ( import (
"encoding/pem" "encoding/pem"
"flag" "flag"
"fmt" "io"
"io/ioutil"
"os" "os"
"git.wntrmute.dev/kyle/goutils/lib" "git.wntrmute.dev/kyle/goutils/lib"
@@ -21,9 +20,9 @@ func main() {
path := flag.Arg(0) path := flag.Arg(0)
if path == "-" { if path == "-" {
in, err = ioutil.ReadAll(os.Stdin) in, err = io.ReadAll(os.Stdin)
} else { } else {
in, err = ioutil.ReadFile(flag.Arg(0)) in, err = os.ReadFile(flag.Arg(0))
} }
if err != nil { if err != nil {
lib.Err(lib.ExitFailure, err, "couldn't read file") lib.Err(lib.ExitFailure, err, "couldn't read file")
@@ -33,5 +32,7 @@ func main() {
if p == nil { if p == nil {
lib.Errx(lib.ExitFailure, "%s isn't a PEM-encoded file", flag.Arg(0)) lib.Errx(lib.ExitFailure, "%s isn't a PEM-encoded file", flag.Arg(0))
} }
fmt.Printf("%s", p.Bytes) if _, err := os.Stdout.Write(p.Bytes); err != nil {
lib.Err(lib.ExitFailure, err, "writing body")
}
} }

View File

@@ -5,7 +5,6 @@ import (
"encoding/pem" "encoding/pem"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"os" "os"
) )
@@ -13,13 +12,13 @@ func main() {
flag.Parse() flag.Parse()
for _, fileName := range flag.Args() { for _, fileName := range flag.Args() {
data, err := ioutil.ReadFile(fileName) data, err := os.ReadFile(fileName)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "[!] %s: %v\n", fileName, err) fmt.Fprintf(os.Stderr, "[!] %s: %v\n", fileName, err)
continue continue
} }
fmt.Printf("[+] %s:\n", fileName) fmt.Fprintf(os.Stdout, "[+] %s:\n", fileName)
rest := data[:] rest := data[:]
for { for {
var p *pem.Block var p *pem.Block
@@ -34,7 +33,7 @@ func main() {
break break
} }
fmt.Printf("\t%+v\n", cert.Subject.CommonName) fmt.Fprintf(os.Stdout, "\t%+v\n", cert.Subject.CommonName)
} }
} }
} }

View File

@@ -110,25 +110,25 @@ func main() {
for _, file := range flag.Args() { for _, file := range flag.Args() {
renamed, err := newName(file) renamed, err := newName(file)
if err != nil { if err != nil {
lib.Warn(err, "failed to get new file name") _, _ = lib.Warn(err, "failed to get new file name")
continue continue
} }
if verbose && !printChanged { if verbose && !printChanged {
fmt.Println(file) fmt.Fprintln(os.Stdout, file)
} }
if renamed != file { if renamed != file {
if !dryRun { if !dryRun {
err = move(renamed, file, force) err = move(renamed, file, force)
if err != nil { if err != nil {
lib.Warn(err, "failed to rename file from %s to %s", file, renamed) _, _ = lib.Warn(err, "failed to rename file from %s to %s", file, renamed)
continue continue
} }
} }
if printChanged && !verbose { if printChanged && !verbose {
fmt.Println(file, "->", renamed) fmt.Fprintln(os.Stdout, file, "->", renamed)
} }
} }
} }

View File

@@ -66,24 +66,19 @@ func main() {
for _, remote := range flag.Args() { for _, remote := range flag.Args() {
u, err := url.Parse(remote) u, err := url.Parse(remote)
if err != nil { if err != nil {
lib.Warn(err, "parsing %s", remote) _, _ = lib.Warn(err, "parsing %s", remote)
continue continue
} }
name := filepath.Base(u.Path) name := filepath.Base(u.Path)
if name == "" { if name == "" {
lib.Warnx("source URL doesn't appear to name a file") _, _ = lib.Warnx("source URL doesn't appear to name a file")
continue continue
} }
resp, err := http.Get(remote) resp, err := http.Get(remote)
if err != nil { if err != nil {
lib.Warn(err, "fetching %s", remote) _, _ = lib.Warn(err, "fetching %s", remote)
continue
}
if err != nil {
lib.Warn(err, "fetching %s", remote)
continue continue
} }

View File

@@ -3,7 +3,7 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"math/rand" "math/rand/v2"
"os" "os"
"regexp" "regexp"
"strconv" "strconv"
@@ -18,7 +18,7 @@ func rollDie(count, sides int) []int {
var rolls []int var rolls []int
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
roll := rand.Intn(sides) + 1 roll := rand.IntN(sides) + 1
sum += roll sum += roll
rolls = append(rolls, roll) rolls = append(rolls, roll)
} }

View File

@@ -13,7 +13,6 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"strings" "strings"
@@ -40,13 +39,13 @@ func init() {
} }
func parse(path string) (public []byte, kt, ft string) { func parse(path string) (public []byte, kt, ft string) {
data, err := ioutil.ReadFile(path) data, err := os.ReadFile(path)
die.If(err) die.If(err)
data = bytes.TrimSpace(data) data = bytes.TrimSpace(data)
p, rest := pem.Decode(data) p, rest := pem.Decode(data)
if len(rest) > 0 { if len(rest) > 0 {
lib.Warnx("trailing data in PEM file") _, _ = lib.Warnx("trailing data in PEM file")
} }
if p == nil { if p == nil {
@@ -85,12 +84,12 @@ func parseKey(data []byte) (public []byte, kt string) {
} }
var priv crypto.Signer var priv crypto.Signer
switch privInterface.(type) { switch p := privInterface.(type) {
case *rsa.PrivateKey: case *rsa.PrivateKey:
priv = privInterface.(*rsa.PrivateKey) priv = p
kt = "RSA" kt = "RSA"
case *ecdsa.PrivateKey: case *ecdsa.PrivateKey:
priv = privInterface.(*ecdsa.PrivateKey) priv = p
kt = "ECDSA" kt = "ECDSA"
default: default:
die.With("unknown private key type %T", privInterface) die.With("unknown private key type %T", privInterface)
@@ -172,7 +171,7 @@ func main() {
var subPKI subjectPublicKeyInfo var subPKI subjectPublicKeyInfo
_, err := asn1.Unmarshal(public, &subPKI) _, err := asn1.Unmarshal(public, &subPKI)
if err != nil { if err != nil {
lib.Warn(err, "failed to get subject PKI") _, _ = lib.Warn(err, "failed to get subject PKI")
continue continue
} }
@@ -183,7 +182,7 @@ func main() {
} }
if shouldMatch && ski != pubHashString { if shouldMatch && ski != pubHashString {
lib.Warnx("%s: SKI mismatch (%s != %s)", _, _ = lib.Warnx("%s: SKI mismatch (%s != %s)",
path, ski, pubHashString) path, ski, pubHashString)
} }
fmt.Printf("%s %s (%s %s)\n", path, pubHashString, kt, ft) fmt.Printf("%s %s (%s %s)\n", path, pubHashString, kt, ft)

View File

@@ -3,10 +3,10 @@ package main
import ( import (
"flag" "flag"
"io" "io"
"log"
"net" "net"
"git.wntrmute.dev/kyle/goutils/die" "git.wntrmute.dev/kyle/goutils/die"
"git.wntrmute.dev/kyle/goutils/lib"
) )
func proxy(conn net.Conn, inside string) error { func proxy(conn net.Conn, inside string) error {
@@ -19,7 +19,7 @@ func proxy(conn net.Conn, inside string) error {
defer conn.Close() defer conn.Close()
go func() { go func() {
io.Copy(conn, proxyConn) _, _ = io.Copy(conn, proxyConn)
}() }()
_, err = io.Copy(proxyConn, conn) _, err = io.Copy(proxyConn, conn)
return err return err
@@ -37,10 +37,14 @@ func main() {
for { for {
conn, err := l.Accept() conn, err := l.Accept()
if err != nil { if err != nil {
log.Println(err) _, _ = lib.Warn(err, "accept failed")
continue continue
} }
go proxy(conn, "127.0.0.1:"+inside) go func() {
if err := proxy(conn, "127.0.0.1:"+inside); err != nil {
_, _ = lib.Warn(err, "proxy error")
}
}()
} }
} }

View File

@@ -8,7 +8,6 @@ import (
"encoding/pem" "encoding/pem"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"net" "net"
"os" "os"
@@ -47,7 +46,7 @@ func main() {
} }
cfg.Certificates = append(cfg.Certificates, cert) cfg.Certificates = append(cfg.Certificates, cert)
if sysRoot != "" { if sysRoot != "" {
pemList, err := ioutil.ReadFile(sysRoot) pemList, err := os.ReadFile(sysRoot)
die.If(err) die.If(err)
roots := x509.NewCertPool() roots := x509.NewCertPool()
@@ -69,38 +68,42 @@ func main() {
conn, err := l.Accept() conn, err := l.Accept()
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
continue
} }
handleConn(conn, cfg)
}
}
// handleConn performs a TLS handshake, extracts the peer chain, and writes it to a file.
func handleConn(conn net.Conn, cfg *tls.Config) {
defer conn.Close()
raddr := conn.RemoteAddr() raddr := conn.RemoteAddr()
tconn := tls.Server(conn, cfg) tconn := tls.Server(conn, cfg)
err = tconn.Handshake() if err := tconn.Handshake(); err != nil {
if err != nil {
fmt.Printf("[+] %v: failed to complete handshake: %v\n", raddr, err) fmt.Printf("[+] %v: failed to complete handshake: %v\n", raddr, err)
continue return
} }
cs := tconn.ConnectionState() cs := tconn.ConnectionState()
if len(cs.PeerCertificates) == 0 { if len(cs.PeerCertificates) == 0 {
fmt.Printf("[+] %v: no chain presented\n", raddr) fmt.Printf("[+] %v: no chain presented\n", raddr)
continue return
} }
var chain []byte var chain []byte
for _, cert := range cs.PeerCertificates { for _, cert := range cs.PeerCertificates {
p := &pem.Block{ p := &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
Type: "CERTIFICATE",
Bytes: cert.Raw,
}
chain = append(chain, pem.EncodeToMemory(p)...) chain = append(chain, pem.EncodeToMemory(p)...)
} }
var nonce [16]byte var nonce [16]byte
_, err = rand.Read(nonce[:]) if _, err := rand.Read(nonce[:]); err != nil {
if err != nil { fmt.Printf("[+] %v: failed to generate filename nonce: %v\n", raddr, err)
panic(err) return
} }
fname := fmt.Sprintf("%v-%v.pem", raddr, hex.EncodeToString(nonce[:])) fname := fmt.Sprintf("%v-%v.pem", raddr, hex.EncodeToString(nonce[:]))
err = ioutil.WriteFile(fname, chain, 0644) if err := os.WriteFile(fname, chain, 0o644); err != nil {
die.If(err) fmt.Printf("[+] %v: failed to write %v: %v\n", raddr, fname, err)
fmt.Printf("%v: [+] wrote %v.\n", raddr, fname) return
} }
fmt.Printf("%v: [+] wrote %v.\n", raddr, fname)
} }

View File

@@ -60,7 +60,7 @@ func printDigests(paths []string, issuer bool) {
for _, path := range paths { for _, path := range paths {
cert, err := certlib.LoadCertificate(path) cert, err := certlib.LoadCertificate(path)
if err != nil { if err != nil {
lib.Warn(err, "failed to load certificate from %s", path) _, _ = lib.Warn(err, "failed to load certificate from %s", path)
continue continue
} }
@@ -88,7 +88,7 @@ func matchDigests(paths []string, issuer bool) {
sndCert, err := certlib.LoadCertificate(snd) sndCert, err := certlib.LoadCertificate(snd)
die.If(err) die.If(err)
if !bytes.Equal(getSubjectInfoHash(fstCert, issuer), getSubjectInfoHash(sndCert, issuer)) { if !bytes.Equal(getSubjectInfoHash(fstCert, issuer), getSubjectInfoHash(sndCert, issuer)) {
lib.Warnx("certificates don't match: %s and %s", fst, snd) _, _ = lib.Warnx("certificates don't match: %s and %s", fst, snd)
invalid++ invalid++
} }
} }

View File

@@ -11,8 +11,6 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"log"
"os" "os"
"git.wntrmute.dev/kyle/goutils/die" "git.wntrmute.dev/kyle/goutils/die"
@@ -52,8 +50,75 @@ func getECCurve(pub interface{}) int {
} }
} }
// matchRSA compares an RSA public key from certificate against RSA public key from private key.
// It returns true on match.
func matchRSA(certPub *rsa.PublicKey, keyPub *rsa.PublicKey) bool {
return keyPub.N.Cmp(certPub.N) == 0 && keyPub.E == certPub.E
}
// matchECDSA compares ECDSA public keys for equality and compatible curve.
// It returns match=true when they are on the same curve and have the same X/Y.
// If curves mismatch, match is false.
func matchECDSA(certPub *ecdsa.PublicKey, keyPub *ecdsa.PublicKey) bool {
if getECCurve(certPub) != getECCurve(keyPub) {
return false
}
if keyPub.X.Cmp(certPub.X) != 0 {
return false
}
if keyPub.Y.Cmp(certPub.Y) != 0 {
return false
}
return true
}
// matchKeys determines whether the certificate's public key matches the given private key.
// It returns true if they match; otherwise, it returns false and a human-friendly reason.
func matchKeys(cert *x509.Certificate, priv crypto.Signer) (bool, string) {
switch keyPub := priv.Public().(type) {
case *rsa.PublicKey:
switch certPub := cert.PublicKey.(type) {
case *rsa.PublicKey:
if matchRSA(certPub, keyPub) {
return true, ""
}
return false, "public keys don't match"
case *ecdsa.PublicKey:
return false, "RSA private key, EC public key"
default:
return false, fmt.Sprintf("unsupported certificate public key type: %T", cert.PublicKey)
}
case *ecdsa.PublicKey:
switch certPub := cert.PublicKey.(type) {
case *ecdsa.PublicKey:
if matchECDSA(certPub, keyPub) {
return true, ""
}
// Determine a more precise reason
kc := getECCurve(keyPub)
cc := getECCurve(certPub)
if kc == curveInvalid {
return false, "invalid private key curve"
}
if cc == curveRSA {
return false, "private key is EC, certificate is RSA"
}
if kc != cc {
return false, "EC curves don't match"
}
return false, "public keys don't match"
case *rsa.PublicKey:
return false, "private key is EC, certificate is RSA"
default:
return false, fmt.Sprintf("unsupported certificate public key type: %T", cert.PublicKey)
}
default:
return false, fmt.Sprintf("unrecognised private key type: %T", priv.Public())
}
}
func loadKey(path string) (crypto.Signer, error) { func loadKey(path string) (crypto.Signer, error) {
in, err := ioutil.ReadFile(path) in, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -78,15 +143,15 @@ func loadKey(path string) (crypto.Signer, error) {
} }
} }
switch priv.(type) { switch p := priv.(type) {
case *rsa.PrivateKey: case *rsa.PrivateKey:
return priv.(*rsa.PrivateKey), nil return p, nil
case *ecdsa.PrivateKey: case *ecdsa.PrivateKey:
return priv.(*ecdsa.PrivateKey), nil return p, nil
} default:
// should never reach here // should never reach here
return nil, errors.New("invalid private key") return nil, errors.New("invalid private key")
}
} }
@@ -96,7 +161,7 @@ func main() {
flag.StringVar(&certFile, "c", "", "TLS `certificate` file") flag.StringVar(&certFile, "c", "", "TLS `certificate` file")
flag.Parse() flag.Parse()
in, err := ioutil.ReadFile(certFile) in, err := os.ReadFile(certFile)
die.If(err) die.If(err)
p, _ := pem.Decode(in) p, _ := pem.Decode(in)
@@ -112,50 +177,11 @@ func main() {
priv, err := loadKey(keyFile) priv, err := loadKey(keyFile)
die.If(err) die.If(err)
switch pub := priv.Public().(type) { matched, reason := matchKeys(cert, priv)
case *rsa.PublicKey: if matched {
switch certPub := cert.PublicKey.(type) {
case *rsa.PublicKey:
if pub.N.Cmp(certPub.N) != 0 || pub.E != certPub.E {
fmt.Println("No match (public keys don't match).")
os.Exit(1)
}
fmt.Println("Match.") fmt.Println("Match.")
return return
case *ecdsa.PublicKey:
fmt.Println("No match (RSA private key, EC public key).")
os.Exit(1)
} }
case *ecdsa.PublicKey: fmt.Printf("No match (%s).\n", reason)
privCurve := getECCurve(pub)
certCurve := getECCurve(cert.PublicKey)
log.Printf("priv: %d\tcert: %d\n", privCurve, certCurve)
if certCurve == curveRSA {
fmt.Println("No match (private key is EC, certificate is RSA).")
os.Exit(1) os.Exit(1)
} else if privCurve == curveInvalid {
fmt.Println("No match (invalid private key curve).")
os.Exit(1)
} else if privCurve != certCurve {
fmt.Println("No match (EC curves don't match).")
os.Exit(1)
}
certPub := cert.PublicKey.(*ecdsa.PublicKey)
if pub.X.Cmp(certPub.X) != 0 {
fmt.Println("No match (public keys don't match).")
os.Exit(1)
}
if pub.Y.Cmp(certPub.Y) != 0 {
fmt.Println("No match (public keys don't match).")
os.Exit(1)
}
fmt.Println("Match.")
default:
fmt.Printf("Unrecognised private key type: %T\n", priv.Public())
os.Exit(1)
}
} }

View File

@@ -123,13 +123,14 @@ func main() {
for _, path := range pathList { for _, path := range pathList {
if isDir(path) { if isDir(path) {
err := filepath.Walk(path, buildWalker(search)) if err := filepath.Walk(path, buildWalker(search)); err != nil {
if err != nil {
errorf("%v", err) errorf("%v", err)
return return
} }
} else { } else {
searchFile(path, search) if err := searchFile(path, search); err != nil {
errorf("%v", err)
}
} }
} }
} }

View File

@@ -83,9 +83,9 @@ var (
func Duration(d time.Duration) string { func Duration(d time.Duration) string {
var s string var s string
if d >= yearDuration { if d >= yearDuration {
years := d / yearDuration years := int64(d / yearDuration)
s += fmt.Sprintf("%dy", years) s += fmt.Sprintf("%dy", years)
d -= years * yearDuration d -= time.Duration(years) * yearDuration
} }
if d >= dayDuration { if d >= dayDuration {
@@ -98,8 +98,8 @@ func Duration(d time.Duration) string {
} }
d %= 1 * time.Second d %= 1 * time.Second
hours := d / time.Hour hours := int64(d / time.Hour)
d -= hours * time.Hour d -= time.Duration(hours) * time.Hour
s += fmt.Sprintf("%dh%s", hours, d) s += fmt.Sprintf("%dh%s", hours, d)
return s return s
} }

View File

@@ -1,6 +1,7 @@
package logging package logging
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
) )
@@ -61,9 +62,9 @@ func NewSplitFile(outpath, errpath string, overwrite bool) (*File, error) {
if err != nil { if err != nil {
if closeErr := fl.Close(); closeErr != nil { if closeErr := fl.Close(); closeErr != nil {
return nil, fmt.Errorf("failed to open error log: cleanup close failed: %v: %w", closeErr, err) return nil, fmt.Errorf("failed to open error log: %w", errors.Join(closeErr, err))
} }
return nil, err return nil, fmt.Errorf("failed to open error log: %w", err)
} }
fl.LogWriter = NewLogWriter(fl.fo, fl.fe) fl.LogWriter = NewLogWriter(fl.fo, fl.fe)