aboutsummaryrefslogtreecommitdiff
path: root/cmd/x509info/main.go
diff options
context:
space:
mode:
authorFranck Cuny <franck@fcuny.net>2024-01-22 08:07:58 -0800
committerFranck Cuny <franck@fcuny.net>2024-01-22 08:07:58 -0800
commitffd20492d19f547de3456249ed374ba752c2e1ab (patch)
tree90f587e26e77dfd28147e830fa0d87bf7ade28dd /cmd/x509info/main.go
parentdisable linter for yaml (diff)
downloadinfra-ffd20492d19f547de3456249ed374ba752c2e1ab.tar.gz
build all the binaries using a Makefile
Add a Makefile to build the local binaries. Rename all the commands without a dash. We can build the commands with `make all` or by being explicit, for example `make bin/x509-info`. Add a common package to keep track of build information (commit and build date) so we can reuse the same pattern across all the commands.
Diffstat (limited to 'cmd/x509info/main.go')
-rw-r--r--cmd/x509info/main.go150
1 files changed, 150 insertions, 0 deletions
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)
+ }
+}