From b9deec27f01f9033f0595c5e22505992c17a2ee6 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Fri, 14 Oct 2016 09:50:22 -0700 Subject: [PATCH] Allow fetching certificates from servers. --- cmd/certdump/certdump.go | 66 ++++++++++++++++++++++++++++++++++++---- cmd/certdump/util.go | 47 ++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/cmd/certdump/certdump.go b/cmd/certdump/certdump.go index 27b9d37..01d5061 100644 --- a/cmd/certdump/certdump.go +++ b/cmd/certdump/certdump.go @@ -6,6 +6,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" + "crypto/tls" "crypto/x509" "crypto/x509/pkix" "flag" @@ -221,6 +222,55 @@ func displayAllCerts(in []byte, leafOnly bool) { } } +func displayAllCertsWeb(uri string, leafOnly bool) { + ci := getConnInfo(uri) + conn, err := tls.Dial("tcp", ci.Addr, permissiveConfig()) + if err != nil { + Warn(err, "couldn't connect to %s", ci.Addr) + return + } + defer conn.Close() + + state := conn.ConnectionState() + conn.Close() + + conn, err = tls.Dial("tcp", ci.Addr, verifyConfig(ci.Host)) + if err == nil { + err = conn.VerifyHostname(ci.Host) + if err == nil { + state = conn.ConnectionState() + } + } else { + Warn(err, "TLS verification error with server name %s", ci.Host) + } + conn.Close() + + if len(state.PeerCertificates) == 0 { + Warnx("no certificates found") + return + } + + if leafOnly { + displayCert(state.PeerCertificates[0]) + return + } + + if len(state.VerifiedChains) == 0 { + Warnx("no verified chains found; using peer chain") + for i := range state.PeerCertificates { + displayCert(state.PeerCertificates[i]) + } + } else { + fmt.Println("TLS chain verified successfully.") + for i := range state.VerifiedChains { + fmt.Printf("--- Verified certificate chain %d ---\n", i+1) + for j := range state.VerifiedChains[i] { + displayCert(state.VerifiedChains[i][j]) + } + } + } +} + func main() { var leafOnly bool flag.StringVar(&dateFormat, "s", oneTrueDateFormat, "date `format` in Go time format") @@ -242,13 +292,17 @@ func main() { } else { for _, filename := range flag.Args() { fmt.Printf("--%s ---\n", filename) - in, err := ioutil.ReadFile(filename) - if err != nil { - Warn(err, "couldn't read certificate") - continue - } + if strings.HasPrefix(filename, "https://") { + displayAllCertsWeb(filename, leafOnly) + } else { + in, err := ioutil.ReadFile(filename) + if err != nil { + Warn(err, "couldn't read certificate") + continue + } - displayAllCerts(in, leafOnly) + displayAllCerts(in, leafOnly) + } } } } diff --git a/cmd/certdump/util.go b/cmd/certdump/util.go index 4f0a169..c4a577a 100644 --- a/cmd/certdump/util.go +++ b/cmd/certdump/util.go @@ -1,9 +1,11 @@ package main import ( + "crypto/tls" "crypto/x509" "errors" "fmt" + "net" "os" "strings" @@ -158,3 +160,48 @@ func dumpHex(in []byte) string { return strings.Trim(s, ":") } + +// permissiveConfig returns a maximally-accepting TLS configuration; +// the purpose is to look at the cert, not verify the security properties +// of the connection. +func permissiveConfig() *tls.Config { + return &tls.Config{ + InsecureSkipVerify: true, + } +} + +// verifyConfig returns a config that will verify the connection. +func verifyConfig(hostname string) *tls.Config { + return &tls.Config{ + ServerName: hostname, + } +} + +type connInfo struct { + // The original URI provided. + URI string + + // The hostname of the server. + Host string + + // The port to connect on. + Port string + + // The address to connect to. + Addr string +} + +func getConnInfo(uri string) *connInfo { + ci := &connInfo{URI: uri} + ci.Host = uri[len("https://"):] + + host, port, err := net.SplitHostPort(ci.Host) + if err != nil { + ci.Port = "443" + } else { + ci.Host = host + ci.Port = port + } + ci.Addr = net.JoinHostPort(ci.Host, ci.Port) + return ci +}