diff --git a/.golangci.yml b/.golangci.yml index 66036a0..d109533 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -450,6 +450,8 @@ linters: linters: [ forbidigo ] - path: 'logging/example_test.go' linters: [ testableexamples ] + - path: 'main.go' + linters: [ forbidigo, mnd ] - source: 'TODO' linters: [ godot ] - text: 'should have a package comment' diff --git a/cmd/atping/main.go b/cmd/atping/main.go index 7a2f1cd..f785d1e 100644 --- a/cmd/atping/main.go +++ b/cmd/atping/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "net" @@ -28,10 +29,16 @@ func connect(addr string, dport string, six bool, timeout time.Duration) error { if verbose { fmt.Printf("connecting to %s/%s... ", addr, proto) - os.Stdout.Sync() + if err = os.Stdout.Sync(); err != nil { + return err + } } - conn, err := net.DialTimeout(proto, addr, timeout) + dialer := &net.Dialer{ + Timeout: timeout, + } + + conn, err := dialer.DialContext(context.Background(), proto, addr) if err != nil { if verbose { fmt.Println("failed.") @@ -42,8 +49,8 @@ func connect(addr string, dport string, six bool, timeout time.Duration) error { if verbose { fmt.Println("OK") } - conn.Close() - return nil + + return conn.Close() } func main() { diff --git a/cmd/ca-signed/main.go b/cmd/ca-signed/main.go index 0e8696f..e2981ce 100644 --- a/cmd/ca-signed/main.go +++ b/cmd/ca-signed/main.go @@ -3,6 +3,7 @@ package main import ( "crypto/x509" "embed" + "errors" "fmt" "os" "path/filepath" @@ -14,22 +15,22 @@ import ( // loadCertsFromFile attempts to parse certificates from a file that may be in // PEM or DER/PKCS#7 format. Returns the parsed certificates or an error. func loadCertsFromFile(path string) ([]*x509.Certificate, error) { + var certs []*x509.Certificate + data, err := os.ReadFile(path) if err != nil { return nil, err } - // Try PEM first - if certs, err := certlib.ParseCertificatesPEM(data); err == nil { + if certs, err = certlib.ParseCertificatesPEM(data); err == nil { return certs, nil } - // Try DER/PKCS7/PKCS12 (with no password) - if certs, _, err := certlib.ParseCertificatesDER(data, ""); err == nil { + if certs, _, err = certlib.ParseCertificatesDER(data, ""); err == nil { return certs, nil - } else { - return nil, err } + + return nil, err } func makePoolFromFile(path string) (*x509.CertPool, error) { @@ -56,49 +57,50 @@ var embeddedTestdata embed.FS // loadCertsFromBytes attempts to parse certificates from bytes that may be in // PEM or DER/PKCS#7 format. func loadCertsFromBytes(data []byte) ([]*x509.Certificate, error) { - // Try PEM first - if certs, err := certlib.ParseCertificatesPEM(data); err == nil { + certs, err := certlib.ParseCertificatesPEM(data) + if err == nil { return certs, nil } - // Try DER/PKCS7/PKCS12 (with no password) - if certs, _, err := certlib.ParseCertificatesDER(data, ""); err == nil { + + certs, _, err = certlib.ParseCertificatesDER(data, "") + if err == nil { return certs, nil - } else { - return nil, err } + + return nil, err } func makePoolFromBytes(data []byte) (*x509.CertPool, error) { - certs, err := loadCertsFromBytes(data) - if err != nil || len(certs) == 0 { - return nil, fmt.Errorf("failed to load CA certificates from embedded bytes") - } - pool := x509.NewCertPool() - for _, c := range certs { - pool.AddCert(c) - } - return pool, nil + certs, err := loadCertsFromBytes(data) + if err != nil || len(certs) == 0 { + return nil, errors.New("failed to load CA certificates from embedded bytes") + } + pool := x509.NewCertPool() + for _, c := range certs { + pool.AddCert(c) + } + return pool, nil } // isSelfSigned returns true if the given certificate is self-signed. // It checks that the subject and issuer match and that the certificate's // signature verifies against its own public key. func isSelfSigned(cert *x509.Certificate) bool { - if cert == nil { - return false - } - // Quick check: subject and issuer match - if cert.Subject.String() != cert.Issuer.String() { - return false - } - // Cryptographic check: the certificate is signed by itself - if err := cert.CheckSignatureFrom(cert); err != nil { - return false - } - return true + if cert == nil { + return false + } + // Quick check: subject and issuer match + if cert.Subject.String() != cert.Issuer.String() { + return false + } + // Cryptographic check: the certificate is signed by itself + if err := cert.CheckSignatureFrom(cert); err != nil { + return false + } + return true } -func verifyAgainstCA(caPool *x509.CertPool, path string) (ok bool, expiry string) { +func verifyAgainstCA(caPool *x509.CertPool, path string) (bool, string) { certs, err := loadCertsFromFile(path) if err != nil || len(certs) == 0 { return false, "" @@ -117,14 +119,14 @@ func verifyAgainstCA(caPool *x509.CertPool, path string) (ok bool, expiry string Intermediates: ints, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, } - if _, err := leaf.Verify(opts); err != nil { + if _, err = leaf.Verify(opts); err != nil { return false, "" } return true, leaf.NotAfter.Format("2006-01-02") } -func verifyAgainstCABytes(caPool *x509.CertPool, certData []byte) (ok bool, expiry string) { +func verifyAgainstCABytes(caPool *x509.CertPool, certData []byte) (bool, string) { certs, err := loadCertsFromBytes(certData) if err != nil || len(certs) == 0 { return false, "" @@ -143,92 +145,159 @@ func verifyAgainstCABytes(caPool *x509.CertPool, certData []byte) (ok bool, expi Intermediates: ints, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, } - if _, err := leaf.Verify(opts); err != nil { + if _, err = leaf.Verify(opts); err != nil { return false, "" } return true, leaf.NotAfter.Format("2006-01-02") } -// selftest runs built-in validation using embedded certificates. -func selftest() int { - type testCase struct { - name string - caFile string - certFile string - expectOK bool +type testCase struct { + name string + caFile string + certFile string + expectOK bool +} + +func (tc testCase) Run() error { + caBytes, err := embeddedTestdata.ReadFile(tc.caFile) + if err != nil { + return fmt.Errorf("selftest: failed to read embedded %s: %w", tc.caFile, err) } - cases := []testCase{ - {name: "ISRG Root X1 validates LE E7", caFile: "testdata/isrg-root-x1.pem", certFile: "testdata/le-e7.pem", expectOK: true}, - {name: "ISRG Root X1 does NOT validate Google WR2", caFile: "testdata/isrg-root-x1.pem", certFile: "testdata/goog-wr2.pem", expectOK: false}, - {name: "GTS R1 validates Google WR2", caFile: "testdata/gts-r1.pem", certFile: "testdata/goog-wr2.pem", expectOK: true}, - {name: "GTS R1 does NOT validate LE E7", caFile: "testdata/gts-r1.pem", certFile: "testdata/le-e7.pem", expectOK: false}, - } + certBytes, err := embeddedTestdata.ReadFile(tc.certFile) + if err != nil { + return fmt.Errorf("selftest: failed to read embedded %s: %w", tc.certFile, err) + } - failures := 0 - for _, tc := range cases { - caBytes, err := embeddedTestdata.ReadFile(tc.caFile) + pool, err := makePoolFromBytes(caBytes) + if err != nil || pool == nil { + return fmt.Errorf("selftest: failed to build CA pool for %s: %w", tc.caFile, err) + } + + ok, exp := verifyAgainstCABytes(pool, certBytes) + if ok != tc.expectOK { + return fmt.Errorf("%s: unexpected result: got %v, want %v", tc.name, ok, tc.expectOK) + } + + if ok { + fmt.Printf("%s: OK (expires %s)\n", tc.name, exp) + } + + fmt.Printf("%s: INVALID (as expected)\n", tc.name) + + return nil +} + +var cases = []testCase{ + { + name: "ISRG Root X1 validates LE E7", + caFile: "testdata/isrg-root-x1.pem", + certFile: "testdata/le-e7.pem", + expectOK: true, + }, + { + name: "ISRG Root X1 does NOT validate Google WR2", + caFile: "testdata/isrg-root-x1.pem", + certFile: "testdata/goog-wr2.pem", + expectOK: false, + }, + { + name: "GTS R1 validates Google WR2", + caFile: "testdata/gts-r1.pem", + certFile: "testdata/goog-wr2.pem", + expectOK: true, + }, + { + name: "GTS R1 does NOT validate LE E7", + caFile: "testdata/gts-r1.pem", + certFile: "testdata/le-e7.pem", + expectOK: false, + }, +} + +// selftest runs built-in validation using embedded certificates. +func selftest() int { + failures := 0 + for _, tc := range cases { + err := tc.Run() if err != nil { - fmt.Fprintf(os.Stderr, "selftest: failed to read embedded %s: %v\n", tc.caFile, err) + fmt.Fprintln(os.Stderr, err) failures++ continue } - certBytes, err := embeddedTestdata.ReadFile(tc.certFile) + } + + // Verify that both embedded root CAs are detected as self-signed + roots := []string{"testdata/gts-r1.pem", "testdata/isrg-root-x1.pem"} + for _, root := range roots { + b, err := embeddedTestdata.ReadFile(root) if err != nil { - fmt.Fprintf(os.Stderr, "selftest: failed to read embedded %s: %v\n", tc.certFile, err) + fmt.Fprintf(os.Stderr, "selftest: failed to read embedded %s: %v\n", root, err) failures++ continue } - pool, err := makePoolFromBytes(caBytes) - if err != nil || pool == nil { - fmt.Fprintf(os.Stderr, "selftest: failed to build CA pool for %s: %v\n", tc.caFile, err) + certs, err := loadCertsFromBytes(b) + if err != nil || len(certs) == 0 { + fmt.Fprintf(os.Stderr, "selftest: failed to parse cert(s) from %s: %v\n", root, err) failures++ continue } - ok, exp := verifyAgainstCABytes(pool, certBytes) - if ok != tc.expectOK { - fmt.Printf("%s: unexpected result: got %v, want %v\n", tc.name, ok, tc.expectOK) - failures++ + leaf := certs[0] + if isSelfSigned(leaf) { + fmt.Printf("%s: SELF-SIGNED (as expected)\n", root) } else { - if ok { - fmt.Printf("%s: OK (expires %s)\n", tc.name, exp) - } else { - fmt.Printf("%s: INVALID (as expected)\n", tc.name) - } + fmt.Printf("%s: expected SELF-SIGNED, but was not detected as such\n", root) + failures++ } - } + } - // Verify that both embedded root CAs are detected as self-signed - roots := []string{"testdata/gts-r1.pem", "testdata/isrg-root-x1.pem"} - for _, root := range roots { - b, err := embeddedTestdata.ReadFile(root) - if err != nil { - fmt.Fprintf(os.Stderr, "selftest: failed to read embedded %s: %v\n", root, err) - failures++ - continue - } - certs, err := loadCertsFromBytes(b) - if err != nil || len(certs) == 0 { - fmt.Fprintf(os.Stderr, "selftest: failed to parse cert(s) from %s: %v\n", root, err) - failures++ - continue - } - leaf := certs[0] - if isSelfSigned(leaf) { - fmt.Printf("%s: SELF-SIGNED (as expected)\n", root) - } else { - fmt.Printf("%s: expected SELF-SIGNED, but was not detected as such\n", root) - failures++ - } - } + if failures == 0 { + fmt.Println("selftest: PASS") + return 0 + } + fmt.Fprintf(os.Stderr, "selftest: FAIL (%d failure(s))\n", failures) + return 1 +} - if failures == 0 { - fmt.Println("selftest: PASS") - return 0 - } - fmt.Fprintf(os.Stderr, "selftest: FAIL (%d failure(s))\n", failures) - return 1 +// expiryString returns a YYYY-MM-DD date string to display for certificate +// expiry. If an explicit exp string is provided, it is used. Otherwise, if a +// leaf certificate is available, its NotAfter is formatted. As a last resort, +// it falls back to today's date (should not normally happen). +func expiryString(leaf *x509.Certificate, exp string) string { + if exp != "" { + return exp + } + if leaf != nil { + return leaf.NotAfter.Format("2006-01-02") + } + return time.Now().Format("2006-01-02") +} + +// processCert verifies a single certificate file against the provided CA pool +// and prints the result in the required format, handling self-signed +// certificates specially. +func processCert(caPool *x509.CertPool, certPath string) { + ok, exp := verifyAgainstCA(caPool, certPath) + name := filepath.Base(certPath) + + // Try to load the leaf cert for self-signed detection and expiry fallback + var leaf *x509.Certificate + if certs, err := loadCertsFromFile(certPath); err == nil && len(certs) > 0 { + leaf = certs[0] + } + + // Prefer the SELF-SIGNED label if applicable + if isSelfSigned(leaf) { + fmt.Printf("%s: SELF-SIGNED\n", name) + return + } + + if ok { + fmt.Printf("%s: OK (expires %s)\n", name, expiryString(leaf, exp)) + return + } + fmt.Printf("%s: INVALID\n", name) } func main() { @@ -250,38 +319,7 @@ func main() { os.Exit(1) } - for _, certPath := range os.Args[2:] { - ok, exp := verifyAgainstCA(caPool, certPath) - name := filepath.Base(certPath) - // Load the leaf once for self-signed detection and potential expiry fallback - var leaf *x509.Certificate - if certs, err := loadCertsFromFile(certPath); err == nil && len(certs) > 0 { - leaf = certs[0] - } - - // If the certificate is self-signed, prefer the SELF-SIGNED label - if isSelfSigned(leaf) { - fmt.Printf("%s: SELF-SIGNED\n", name) - continue - } - - if ok { - // Display with the requested format - // Example: file: OK (expires 2031-01-01) - // Ensure deterministic date formatting - // Note: no timezone displayed; date only as per example - // If exp ended up empty for some reason, recompute safely - if exp == "" { - if leaf != nil { - exp = leaf.NotAfter.Format("2006-01-02") - } else { - // fallback to the current date to avoid empty; though shouldn't happen - exp = time.Now().Format("2006-01-02") - } - } - fmt.Printf("%s: OK (expires %s)\n", name, exp) - } else { - fmt.Printf("%s: INVALID\n", name) - } - } + for _, certPath := range os.Args[2:] { + processCert(caPool, certPath) + } }