aboutsummaryrefslogtreecommitdiff
path: root/cmd/x509info
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/x509info')
-rw-r--r--cmd/x509info/README.md54
-rw-r--r--cmd/x509info/main.go150
2 files changed, 204 insertions, 0 deletions
diff --git a/cmd/x509info/README.md b/cmd/x509info/README.md
new file mode 100644
index 0000000..479771c
--- /dev/null
+++ b/cmd/x509info/README.md
@@ -0,0 +1,54 @@
+# x509-info
+
+At this point it's pretty clear that I'll never remember the syntax for `openssl` to show various information about a certificate. At last I will not have to google for that syntax ever again.
+
+## Usage
+
+``` shell
+Usage:
+ x509-info [DOMAIN]
+ x509-info (-f long) [DOMAIN]
+
+Options:
+ -f, --format Format the result. Valid values: short, long. Default: short
+ -i, --insecure Skip the TLS validation. Default: false
+ -p, --port Specify the port. Default: 443
+ -v, --version Print version information
+ -h, --help Print this message
+```
+
+The default format will print a short message:
+``` shell
+$ ./bin/x509-info github.com
+github.com, valid until Thu, 14 Mar 2024 23:59:59 UTC (86 days left)
+```
+
+It's possible to get more details:
+``` shell
+$ ./bin/x509-info -f long github.com
+certificate
+ version: 3
+ serial: 17034156255497985825694118641198758684
+ subject: github.com
+ issuer: DigiCert TLS Hybrid ECC SHA384 2020 CA1
+
+validity:
+ not before: Tue, 14 Feb 2023 00:00:00 UTC
+ not after: Thu, 14 Mar 2024 23:59:59 UTC
+ validity days: 394
+ remaining days: 86
+
+SANs:
+ • github.com
+ • www.github.com
+```
+
+You can also check expired certificates:
+``` shell
+$ ./bin/x509-info -i expired.badssl.com
+*.badssl.com, not valid since Sun, 12 Apr 2015 23:59:59 UTC (expired 3172 days ago)
+```
+
+## Notes
+
+Could the same be achieved with a wrapper around `openssl` ? yes.
diff --git a/cmd/x509info/main.go b/cmd/x509info/main.go
new file mode 100644
index 0000000..c425c45
--- /dev/null
+++ b/cmd/x509info/main.go
@@ -0,0 +1,150 @@
+package main
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "flag"
+ "fmt"
+ "html/template"
+ "os"
+ "time"
+
+ "github.com/fcuny/world/internal/version"
+)
+
+const usage = `Usage:
+ x509-info [DOMAIN]
+ x509-info (-f long) [DOMAIN]
+
+Options:
+ -f, --format Format the result. Valid values: short, long. Default: short
+ -i, --insecure Skip the TLS validation. Default: false
+ -p, --port Specify the port. Default: 443
+ -v, --version Print version information
+ -h, --help Print this message
+`
+
+func main() {
+ flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) }
+
+ var (
+ portFlag int
+ outputFormatFlag string
+ insecureFlag bool
+ versionFlag bool
+ )
+
+ flag.IntVar(&portFlag, "port", 443, "Port to check")
+ flag.IntVar(&portFlag, "p", 443, "Port to check")
+ flag.StringVar(&outputFormatFlag, "format", "short", "Format the output")
+ flag.StringVar(&outputFormatFlag, "f", "short", "Format the output")
+ flag.BoolVar(&insecureFlag, "insecure", false, "Whether to bypass secure flag checks")
+ flag.BoolVar(&insecureFlag, "i", false, "Whether to bypass secure flag checks")
+ flag.BoolVar(&versionFlag, "version", false, "Print version information")
+ flag.BoolVar(&versionFlag, "v", false, "Print version information")
+
+ flag.Parse()
+
+ if versionFlag {
+ information := version.VersionAndBuildInfo()
+ fmt.Println(information)
+ return
+ }
+
+ if flag.NArg() != 1 {
+ fmt.Fprintf(os.Stderr, "too many arguments: got %d, expected 1\n", flag.NArg())
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ domain := flag.Arg(0)
+
+ certs, err := getCertificates(domain, portFlag, insecureFlag)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error: %v\n", err)
+ os.Exit(1)
+ }
+
+ switch outputFormatFlag {
+ case "long":
+ printLong(certs)
+ default:
+ printShort(certs)
+ }
+}
+
+func getCertificates(domain string, port int, insecureSkipVerify bool) ([]*x509.Certificate, error) {
+ conf := &tls.Config{
+ InsecureSkipVerify: insecureSkipVerify,
+ }
+
+ remote := fmt.Sprintf("%s:%d", domain, port)
+
+ conn, err := tls.Dial("tcp", remote, conf)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get the certificate for %s: %v", remote, err)
+ }
+
+ defer conn.Close()
+
+ certs := conn.ConnectionState().PeerCertificates
+ return certs, nil
+}
+
+func printShort(certs []*x509.Certificate) {
+ cert := certs[0]
+
+ now := time.Now()
+ remainingDays := cert.NotAfter.Sub(now)
+
+ if remainingDays > 0 {
+ fmt.Printf("%s, valid until %s (%d days left)\n", cert.Subject.CommonName, cert.NotAfter.Format(time.RFC1123), int(remainingDays.Hours()/24))
+ } else {
+ fmt.Printf("%s, not valid since %s (expired %d days ago)\n", cert.Subject.CommonName, cert.NotAfter.Format(time.RFC1123), int(remainingDays.Abs().Hours()/24))
+ }
+}
+
+const tmplLong = `certificate
+ version: {{ .Version }}
+ serial: {{ .SerialNumber }}
+ subject: {{ .Subject.CommonName }}
+ issuer: {{ .Issuer.CommonName }}
+
+validity:
+ not before: {{ rfc1123 .NotBefore }}
+ not after: {{ rfc1123 .NotAfter }}
+ validity days: {{ validFor .NotBefore .NotAfter }}
+ remaining days: {{ remainingDays .NotAfter }}
+
+SANs:
+{{- range $i, $name := .DNSNames }}
+ • {{ $name }}
+{{- end }}
+`
+
+func printLong(certs []*x509.Certificate) {
+ funcMap := template.FuncMap{
+ "validFor": func(before, after time.Time) int {
+ validForDays := after.Sub(before)
+ return int(validForDays.Hours() / 24)
+ },
+ "remainingDays": func(notAfter time.Time) int {
+ now := time.Now()
+ remainingDays := notAfter.Sub(now)
+ return int(remainingDays.Hours() / 24)
+ },
+ "rfc1123": func(date time.Time) string {
+ return date.Format(time.RFC1123)
+ },
+ }
+
+ tmpl, err := template.New("tmpl").Funcs(funcMap).Parse(tmplLong)
+ if err != nil {
+ panic(err)
+ }
+
+ err = tmpl.Execute(os.Stdout, certs[0])
+ if err != nil {
+ panic(err)
+ }
+}