aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/certcheck/README.org15
-rw-r--r--cmd/certcheck/main.go167
2 files changed, 182 insertions, 0 deletions
diff --git a/cmd/certcheck/README.org b/cmd/certcheck/README.org
new file mode 100644
index 0000000..f1d2eea
--- /dev/null
+++ b/cmd/certcheck/README.org
@@ -0,0 +1,15 @@
+* x509-info
+
+At this point it's pretty clear that I'll never remember the syntax for =opensll= to show various information about a certificate. At last I will not have to google for that syntax ever again.
+
+** Usage
+#+begin_src go
+certcheck example.com
+
+certcheck go run . -domain example.com -port 443 -format long
+
+certcheck -domain self-signed.badssl.com -insecure -format long
+#+end_src
+
+** Notes
+Could the same be achieved with a wrapper around `openssl` ? yes.
diff --git a/cmd/certcheck/main.go b/cmd/certcheck/main.go
new file mode 100644
index 0000000..1140ff9
--- /dev/null
+++ b/cmd/certcheck/main.go
@@ -0,0 +1,167 @@
+package main
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "flag"
+ "fmt"
+ "os"
+ "time"
+)
+
+type OutputFormat string
+
+const (
+ FormatShort OutputFormat = "short"
+ FormatLong OutputFormat = "long"
+)
+
+type Config struct {
+ Domain string
+ Port int
+ Insecure bool
+ Format OutputFormat
+}
+
+func main() {
+ var config Config
+ var formatStr string
+
+ flag.StringVar(&config.Domain, "domain", "", "Domain to check (required)")
+ flag.IntVar(&config.Port, "port", 443, "Port to check")
+ flag.BoolVar(&config.Insecure, "insecure", false, "Accept invalid certificate")
+ flag.StringVar(&formatStr, "format", "short", "Output format (short|long)")
+ flag.Parse()
+
+ if config.Domain == "" {
+ if len(flag.Args()) == 0 {
+ fmt.Fprintf(os.Stderr, "Error: domain is required\n")
+ flag.Usage()
+ os.Exit(1)
+ }
+ config.Domain = flag.Args()[0]
+ }
+
+ switch formatStr {
+ case "short":
+ config.Format = FormatShort
+ case "long":
+ config.Format = FormatLong
+ default:
+ fmt.Fprintf(os.Stderr, "Error: invalid format '%s', must be 'short' or 'long'\n", formatStr)
+ os.Exit(1)
+ }
+
+ cert, err := getCertificate(config.Domain, config.Port, config.Insecure)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error: %v\n", err)
+ os.Exit(1)
+ }
+
+ printCertificate(cert, config.Format)
+}
+
+func getCertificate(domain string, port int, insecure bool) (*x509.Certificate, error) {
+ address := fmt.Sprintf("%s:%d", domain, port)
+
+ tlsConfig := &tls.Config{
+ ServerName: domain,
+ InsecureSkipVerify: insecure,
+ }
+
+ conn, err := tls.Dial("tcp", address, tlsConfig)
+ if err != nil {
+ return nil, fmt.Errorf("failed to connect to %s: %w", address, err)
+ }
+ defer func() {
+ if closeErr := conn.Close(); closeErr != nil {
+ // Log the error but don't override the main function's return value
+ fmt.Fprintf(os.Stderr, "warning: failed to close connection: %v\n", closeErr)
+ }
+ }()
+
+ certs := conn.ConnectionState().PeerCertificates
+ if len(certs) == 0 {
+ return nil, fmt.Errorf("no certificate found for %s", domain)
+ }
+
+ return certs[0], nil
+}
+
+func printCertificate(cert *x509.Certificate, format OutputFormat) {
+ switch format {
+ case FormatShort:
+ printShort(cert)
+ case FormatLong:
+ printLong(cert)
+ }
+}
+
+func printShort(cert *x509.Certificate) {
+ remaining := time.Until(cert.NotAfter)
+
+ commonName := getCommonName(cert)
+
+ if remaining >= 0 {
+ days := int(remaining.Hours() / 24)
+ fmt.Printf("%s: %s (%d days left)\n",
+ commonName,
+ cert.NotAfter.Format(time.RFC1123Z),
+ days)
+ } else {
+ days := int(-remaining.Hours() / 24)
+ fmt.Printf("%s: %s (it expired %d days ago)\n",
+ commonName,
+ cert.NotAfter.Format(time.RFC1123Z),
+ days)
+ }
+}
+
+func printLong(cert *x509.Certificate) {
+ remaining := time.Until(cert.NotAfter)
+ validityDuration := cert.NotAfter.Sub(cert.NotBefore)
+
+ fmt.Println("certificate")
+ fmt.Printf(" version: %d\n", cert.Version)
+ fmt.Printf(" serial: %s\n", cert.SerialNumber.String())
+ fmt.Printf(" subject: %s\n", cert.Subject.String())
+ fmt.Printf(" issuer: %s\n", cert.Issuer.String())
+
+ fmt.Println(" validity")
+ fmt.Printf(" not before : %s\n", cert.NotBefore.Format(time.RFC1123Z))
+ fmt.Printf(" not after : %s\n", cert.NotAfter.Format(time.RFC1123Z))
+ fmt.Printf(" validity days : %d\n", int(validityDuration.Hours()/24))
+ fmt.Printf(" remaining days: %d\n", int(remaining.Hours()/24))
+
+ fmt.Println(" SANs:")
+ printSANs(cert)
+}
+
+func getCommonName(cert *x509.Certificate) string {
+ if cert.Subject.CommonName != "" {
+ return cert.Subject.CommonName
+ }
+ return "<no name>"
+}
+
+func printSANs(cert *x509.Certificate) {
+ // DNS names
+ for _, name := range cert.DNSNames {
+ fmt.Printf(" DNS:%s\n", name)
+ }
+
+ // IP addresses
+ for _, ip := range cert.IPAddresses {
+ fmt.Printf(" IP address:%s\n", ip.String())
+ }
+
+ // Email addresses
+ for _, email := range cert.EmailAddresses {
+ fmt.Printf(" Email:%s\n", email)
+ }
+
+ // URIs
+ for _, uri := range cert.URIs {
+ fmt.Printf(" URI:%s\n", uri.String())
+ }
+}