diff --git a/.golangci.yml b/.golangci.yml index aeb47f4..e160423 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -101,7 +101,7 @@ linters: - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) - makezero # finds slice declarations with non-zero initial length - mirror # reports wrong mirror patterns of bytes/strings usage - - mnd # detects magic numbers + # - mnd # detects magic numbers - modernize # suggests simplifications to Go code, using modern language and library features - musttag # enforces field tags in (un)marshaled structs - nakedret # finds naked returns in functions greater than a specified function length diff --git a/certlib/certgen/config.go b/certlib/certgen/config.go index 5d3e454..dd1d1b7 100644 --- a/certlib/certgen/config.go +++ b/certlib/certgen/config.go @@ -158,7 +158,11 @@ func (p Profile) templateFromRequest(req *x509.CertificateRequest) (*x509.Certif return certTemplate, nil } -func (p Profile) SignRequest(parent *x509.Certificate, req *x509.CertificateRequest, priv crypto.PrivateKey) (*x509.Certificate, error) { +func (p Profile) SignRequest( + parent *x509.Certificate, + req *x509.CertificateRequest, + priv crypto.PrivateKey, +) (*x509.Certificate, error) { tpl, err := p.templateFromRequest(req) if err != nil { return nil, fmt.Errorf("failed to create certificate template: %w", err) diff --git a/certlib/certgen/keygen.go b/certlib/certgen/keygen.go index 299203c..23205d6 100644 --- a/certlib/certgen/keygen.go +++ b/certlib/certgen/keygen.go @@ -8,14 +8,13 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" - "encoding/asn1" "errors" "fmt" ) -var ( - oidEd25519 = asn1.ObjectIdentifier{1, 3, 101, 110} -) +// var ( +// oidEd25519 = asn1.ObjectIdentifier{1, 3, 101, 110} +//) func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicKey, crypto.PrivateKey, error) { var key crypto.PrivateKey @@ -28,7 +27,12 @@ func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicK case x509.Ed25519: key, err = rsa.GenerateKey(rand.Reader, bitSize) if err == nil { - pub = key.(*rsa.PrivateKey).Public() + rsaPriv, ok := key.(*rsa.PrivateKey) + if !ok { + panic("failed to cast RSA private key to *rsa.PrivateKey") + } + + pub = rsaPriv.Public() } case x509.ECDSA: var curve elliptic.Curve @@ -46,8 +50,17 @@ func GenerateKey(algorithm x509.PublicKeyAlgorithm, bitSize int) (crypto.PublicK key, err = ecdsa.GenerateKey(curve, rand.Reader) if err == nil { - pub = key.(*ecdsa.PrivateKey).Public() + ecPriv, ok := key.(*ecdsa.PrivateKey) + if !ok { + panic("failed to cast ECDSA private key to *ecdsa.PrivateKey") + } + + pub = ecPriv.Public() } + case x509.DSA: + fallthrough + case x509.UnknownPublicKeyAlgorithm: + fallthrough default: err = errors.New("unsupported algorithm") } diff --git a/certlib/dump/dump.go b/certlib/dump/dump.go index 9e9be0f..b82076d 100644 --- a/certlib/dump/dump.go +++ b/certlib/dump/dump.go @@ -54,8 +54,6 @@ var extKeyUsages = map[x509.ExtKeyUsage]string{ x509.ExtKeyUsageMicrosoftKernelCodeSigning: "microsoft kernel code signing", } - - func sigAlgoPK(a x509.SignatureAlgorithm) string { switch a { case x509.MD2WithRSA, x509.MD5WithRSA, x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA: diff --git a/certlib/keymatch_test.go b/certlib/keymatch_test.go index 4b8c2b9..79af4fa 100644 --- a/certlib/keymatch_test.go +++ b/certlib/keymatch_test.go @@ -1,6 +1,10 @@ -package certlib +package certlib_test -import "testing" +import ( + "testing" + + "git.wntrmute.dev/kyle/goutils/certlib" +) var ( testCert1 = "testdata/cert1.pem" @@ -16,25 +20,25 @@ type testCase struct { } var testCases = []testCase{ - {"testdata/cert1.pem", "testdata/priv1.pem", true}, - {"testdata/cert2.pem", "testdata/priv2.pem", true}, - {"testdata/cert1.pem", "testdata/priv2.pem", false}, - {"testdata/cert2.pem", "testdata/priv1.pem", false}, + {testCert1, testPriv1, true}, + {testCert2, testPriv2, true}, + {testCert1, testPriv2, false}, + {testCert2, testPriv1, false}, } func TestMatchKeys(t *testing.T) { for i, tc := range testCases { - cert, err := LoadCertificate(tc.cert) + cert, err := certlib.LoadCertificate(tc.cert) if err != nil { t.Fatalf("failed to load cert %d: %v", i, err) } - priv, err := LoadPrivateKey(tc.key) + priv, err := certlib.LoadPrivateKey(tc.key) if err != nil { t.Fatalf("failed to load key %d: %v", i, err) } - ok, _ := MatchKeys(cert, priv) + ok, _ := certlib.MatchKeys(cert, priv) switch { case ok && !tc.match: t.Fatalf("case %d: cert %s/key %s should not match", i, tc.cert, tc.key) diff --git a/cmd/cert-revcheck/main.go b/cmd/cert-revcheck/main.go index cb7925e..9f4ff7c 100644 --- a/cmd/cert-revcheck/main.go +++ b/cmd/cert-revcheck/main.go @@ -39,7 +39,7 @@ func main() { revoke.HardFail = hardfail // Build a proxy-aware HTTP client for OCSP/CRL fetches - if httpClient, err := dialer.NewHTTPClient(dialer.DialerOpts{Timeout: timeout}); err == nil { + if httpClient, err := dialer.NewHTTPClient(dialer.Opts{Timeout: timeout}); err == nil { revoke.HTTPClient = httpClient } @@ -105,7 +105,7 @@ func checkSite(hostport string) (string, error) { defer cancel() // Use proxy-aware TLS dialer - conn, err := dialer.DialTLS(ctx, target.String(), dialer.DialerOpts{Timeout: timeout, TLSConfig: &tls.Config{ + conn, err := dialer.DialTLS(ctx, target.String(), dialer.Opts{Timeout: timeout, TLSConfig: &tls.Config{ InsecureSkipVerify: true, // #nosec G402 -- CLI tool only verifies revocation ServerName: target.Host, }}) diff --git a/cmd/certchain/main.go b/cmd/certchain/main.go index 6c1161e..b0a237c 100644 --- a/cmd/certchain/main.go +++ b/cmd/certchain/main.go @@ -25,7 +25,11 @@ func main() { } // Use proxy-aware TLS dialer - conn, err := dialer.DialTLS(context.Background(), server, dialer.DialerOpts{TLSConfig: &tls.Config{}}) // #nosec G402 + conn, err := dialer.DialTLS( + context.Background(), + server, + dialer.Opts{TLSConfig: &tls.Config{}}, + ) // #nosec G402 die.If(err) defer conn.Close() diff --git a/cmd/rhash/main.go b/cmd/rhash/main.go index 71d7a39..38339d7 100644 --- a/cmd/rhash/main.go +++ b/cmd/rhash/main.go @@ -85,7 +85,7 @@ func main() { continue } // Use proxy-aware HTTP client with a reasonable timeout for connects/handshakes - httpClient, err := dialer.NewHTTPClient(dialer.DialerOpts{Timeout: 30 * time.Second}) + httpClient, err := dialer.NewHTTPClient(dialer.Opts{Timeout: 30 * time.Second}) if err != nil { _, _ = lib.Warn(err, "building HTTP client for %s", remote) continue diff --git a/cmd/stealchain/main.go b/cmd/stealchain/main.go index a5772ef..973928f 100644 --- a/cmd/stealchain/main.go +++ b/cmd/stealchain/main.go @@ -43,7 +43,7 @@ func main() { } var conn *tls.Conn - conn, err = dialer.DialTLS(context.Background(), site, dialer.DialerOpts{TLSConfig: tlsCfg}) + conn, err = dialer.DialTLS(context.Background(), site, dialer.Opts{TLSConfig: tlsCfg}) die.If(err) cs := conn.ConnectionState() diff --git a/cmd/tlsinfo/main.go b/cmd/tlsinfo/main.go index a258eaf..f47caaa 100644 --- a/cmd/tlsinfo/main.go +++ b/cmd/tlsinfo/main.go @@ -25,7 +25,7 @@ func main() { conn, err := dialer.DialTLS( context.Background(), hostPort.String(), - dialer.DialerOpts{TLSConfig: &tls.Config{InsecureSkipVerify: true}}, + dialer.Opts{TLSConfig: &tls.Config{InsecureSkipVerify: true}}, ) // #nosec G402 die.If(err) diff --git a/lib/dialer/dialer.go b/lib/dialer/dialer.go index 09c24da..d541819 100644 --- a/lib/dialer/dialer.go +++ b/lib/dialer/dialer.go @@ -1,5 +1,5 @@ -// Package lib contains reusable helpers. This file provides proxy-aware -// dialers for plain TCP and TLS connections using environment variables. +// Package dialer provides proxy-aware dialers for plain TCP and TLS +// connections using environment variables. // // Supported proxy environment variables (checked case-insensitively): // - SOCKS5_PROXY (e.g., socks5://user:pass@host:1080) @@ -66,7 +66,7 @@ func BaselineTLSConfig(skipVerify bool, secure bool) (*tls.Config, error) { var debug = dbg.NewFromEnv() -// DialerOpts controls creation of proxy-aware dialers. +// Opts controls creation of proxy-aware dialers. // // Timeout controls the maximum amount of time spent establishing the // underlying TCP connection and any proxy handshake. If zero, a @@ -75,7 +75,7 @@ var debug = dbg.NewFromEnv() // TLSConfig is used by the TLS dialer to configure the TLS handshake to // the target endpoint. If TLSConfig.ServerName is empty, it will be set // from the host portion of the address passed to DialContext. -type DialerOpts struct { +type Opts struct { Timeout time.Duration TLSConfig *tls.Config } @@ -88,7 +88,7 @@ type ContextDialer interface { // DialTCP is a convenience helper that dials a TCP connection to address // using a proxy-aware dialer derived from opts. It honors SOCKS5_PROXY, // HTTPS_PROXY, and HTTP_PROXY environment variables. -func DialTCP(ctx context.Context, address string, opts DialerOpts) (net.Conn, error) { +func DialTCP(ctx context.Context, address string, opts Opts) (net.Conn, error) { d, err := NewNetDialer(opts) if err != nil { return nil, err @@ -100,7 +100,7 @@ func DialTCP(ctx context.Context, address string, opts DialerOpts) (net.Conn, er // address using a proxy-aware dialer derived from opts. It returns a *tls.Conn. // It honors SOCKS5_PROXY, HTTPS_PROXY, and HTTP_PROXY environment variables and // uses opts.TLSConfig for the handshake (filling ServerName from address if empty). -func DialTLS(ctx context.Context, address string, opts DialerOpts) (*tls.Conn, error) { +func DialTLS(ctx context.Context, address string, opts Opts) (*tls.Conn, error) { d, err := NewTLSDialer(opts) if err != nil { return nil, err @@ -123,7 +123,7 @@ func DialTLS(ctx context.Context, address string, opts DialerOpts) (*tls.Conn, e // proxies discovered from the environment (SOCKS5_PROXY, HTTPS_PROXY, HTTP_PROXY). // The returned dialer supports context cancellation for direct and HTTP(S) // proxies and applies the configured timeout to connection/proxy handshake. -func NewNetDialer(opts DialerOpts) (ContextDialer, error) { +func NewNetDialer(opts Opts) (ContextDialer, error) { if opts.Timeout <= 0 { opts.Timeout = 30 * time.Second } @@ -165,7 +165,7 @@ func NewNetDialer(opts DialerOpts) (ContextDialer, error) { // // The returned dialer performs proxy negotiation (if any), then completes a // TLS handshake to the target using opts.TLSConfig. -func NewTLSDialer(opts DialerOpts) (ContextDialer, error) { +func NewTLSDialer(opts Opts) (ContextDialer, error) { if opts.Timeout <= 0 { opts.Timeout = 30 * time.Second } @@ -247,7 +247,7 @@ func getProxyURLFromEnv(name string) *url.URL { // HTTPS_PROXY, and NO_PROXY/no_proxy. // - Connection and TLS handshake timeouts are derived from opts.Timeout. // - For HTTPS targets, opts.TLSConfig is applied to the transport. -func NewHTTPClient(opts DialerOpts) (*http.Client, error) { +func NewHTTPClient(opts Opts) (*http.Client, error) { if opts.Timeout <= 0 { opts.Timeout = 30 * time.Second } @@ -422,7 +422,7 @@ func drainHeaders(br *bufio.Reader) error { } // newSOCKS5Dialer builds a context-aware wrapper over the x/net/proxy dialer. -func newSOCKS5Dialer(u *url.URL, opts DialerOpts) (ContextDialer, error) { +func newSOCKS5Dialer(u *url.URL, opts Opts) (ContextDialer, error) { var auth *xproxy.Auth if u.User != nil { user := u.User.Username() @@ -468,8 +468,8 @@ func (s *socks5ContextDialer) DialContext(ctx context.Context, network, address // tlsWrappingDialer performs a TLS handshake over an existing base dialer. type tlsWrappingDialer struct { - base ContextDialer - tcfg *tls.Config + base ContextDialer + tcfg *tls.Config timeout time.Duration } diff --git a/lib/fetch/fetch.go b/lib/fetch/fetch.go index ecd32fa..54512ed 100644 --- a/lib/fetch/fetch.go +++ b/lib/fetch/fetch.go @@ -67,7 +67,7 @@ func (sf *ServerFetcher) String() string { } func (sf *ServerFetcher) GetChain() ([]*x509.Certificate, error) { - opts := dialer.DialerOpts{ + opts := dialer.Opts{ TLSConfig: &tls.Config{ InsecureSkipVerify: sf.insecure, // #nosec G402 - no shit sherlock RootCAs: sf.roots, diff --git a/lib/lib.go b/lib/lib.go index 6938e9d..52d47e9 100644 --- a/lib/lib.go +++ b/lib/lib.go @@ -3,6 +3,7 @@ package lib import ( "encoding/base64" "encoding/hex" + "errors" "fmt" "os" "path/filepath" @@ -118,6 +119,8 @@ func IsDigit(b byte) bool { return b >= '0' && b <= '9' } +const signedaMask64 = 1<<63 - 1 + // ParseDuration parses a duration string into a time.Duration. // It supports standard units (ns, us/µs, ms, s, m, h) plus extended units: // d (days, 24h), w (weeks, 7d), y (years, 365d). @@ -127,7 +130,7 @@ func IsDigit(b byte) bool { func ParseDuration(s string) (time.Duration, error) { s = strings.ToLower(s) // Normalize to lowercase for case-insensitivity. if s == "" { - return 0, fmt.Errorf("empty duration string") + return 0, errors.New("empty duration string") } var total time.Duration @@ -165,23 +168,24 @@ func ParseDuration(s string) (time.Duration, error) { var d time.Duration switch unit { case "ns": - d = time.Nanosecond * time.Duration(num) + d = time.Nanosecond * time.Duration(num&signedaMask64) // #nosec G115 - masked off case "us", "µs": - d = time.Microsecond * time.Duration(num) + d = time.Microsecond * time.Duration(num&signedaMask64) // #nosec G115 - masked off case "ms": - d = time.Millisecond * time.Duration(num) + d = time.Millisecond * time.Duration(num&signedaMask64) // #nosec G115 - masked off case "s": - d = time.Second * time.Duration(num) + d = time.Second * time.Duration(num&signedaMask64) // #nosec G115 - masked off case "m": - d = time.Minute * time.Duration(num) + d = time.Minute * time.Duration(num&signedaMask64) // #nosec G115 - masked off case "h": - d = time.Hour * time.Duration(num) + d = time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off case "d": - d = 24 * time.Hour * time.Duration(num) + d = 24 * time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off case "w": - d = 7 * 24 * time.Hour * time.Duration(num) + d = 7 * 24 * time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off case "y": - d = 365 * 24 * time.Hour * time.Duration(num) // Approximate, non-leap year. + // Approximate, non-leap year. + d = 365 * 24 * time.Hour * time.Duration(num&signedaMask64) // #nosec G115 - masked off; default: return 0, fmt.Errorf("unknown unit %q at position %d", s[unitStart:i], unitStart) }